Browse Source

Migra app Servidores

revisaoSidenav
Sesostris Vieira 3 years ago
parent
commit
5b13db5d1b
  1. 2
      requirements/dev-requirements.txt
  2. 2
      requirements/requirements.txt
  3. 63
      sigi/apps/servidores/admin.py
  4. 20
      sigi/apps/servidores/filters.py
  5. 66
      sigi/apps/servidores/forms.py
  6. 16
      sigi/apps/servidores/migrations/0001_initial.py
  7. 2
      sigi/apps/servidores/migrations/0006_auto_20210429_0822.py
  8. 9
      sigi/apps/servidores/models.py
  9. 14
      sigi/apps/servidores/templates/admin/servidores/servidor/change_list.html
  10. 18
      sigi/apps/servidores/templates/admin/widgets/clearable_file_input.html
  11. 31
      sigi/apps/servidores/test_servidores_forms.py
  12. 18
      sigi/apps/utils/admin_widgets.py
  13. 16
      sigi/settings/base.py
  14. 7
      sigi/settings/development.py
  15. 0
      sigi/templates/base_change_form.html
  16. 8
      sigi/urls.py

2
requirements/dev-requirements.txt

@ -0,0 +1,2 @@
-r requirements.txt
django-debug-toolbar==3.2.4

2
requirements/requirements.txt

@ -2,3 +2,5 @@ ipython==7.30.1
Django==4.0.1 Django==4.0.1
django-extensions==3.1.5 django-extensions==3.1.5
psycopg2==2.9.3 psycopg2==2.9.3
django-bootstrap5==21.3
Pillow==9.0.0

63
sigi/apps/servidores/admin.py

@ -1,45 +1,22 @@
# -*- coding: utf-8 -*- from django.db import models
from django.contrib import admin from django.contrib import admin
from django.contrib.contenttypes import generic from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from sigi.apps.contatos.models import Endereco, Telefone
from sigi.apps.servidores.models import Servidor, Servico from sigi.apps.servidores.models import Servidor, Servico
from sigi.apps.utils.admin_widgets import AdminImageWidget from sigi.apps.servidores.filters import ServicoFilter
from sigi.apps.utils.base_admin import BaseModelAdmin
from sigi.apps.utils.filters import AlphabeticFilter
class ServidorFilter(AlphabeticFilter):
title = _('Nome do Servidor')
parameter_name = 'servidor__nome_completo'
class ServicoFilter(admin.SimpleListFilter):
title = _("Subordinados à")
parameter_name = 'subordinado__id__exact'
def lookups(self, request, model_admin):
return ([('None', _("Nenhum"))] +
[(s.id, s.nome) for s in Servico.objects.exclude(servico=None)])
def queryset(self, request, queryset):
if self.value():
if self.value() == "None":
queryset = queryset.filter(subordinado=None)
else:
queryset = queryset.filter(subordinado__id=self.value())
return queryset
class ServicoInline(admin.TabularInline): class ServicoInline(admin.TabularInline):
model = Servico model = Servico
fields = ['nome', 'sigla', 'responsavel',] fields = ['nome', 'sigla', 'responsavel',]
autocomplete_fields = ['responsavel',]
class ServidorInline(admin.TabularInline): class ServidorInline(admin.TabularInline):
model = Servidor model = Servidor
fields = ('imagem_foto', 'nome_completo', 'is_active', ) fields = ('imagem_foto', 'nome_completo', 'is_active', )
readonly_fields = ('imagem_foto', 'nome_completo', 'is_active', ) readonly_fields = ('imagem_foto', 'nome_completo', 'is_active', )
def has_add_permission(self, request): def has_add_permission(self, request, obj):
return False return False
def has_delete_permission(self, request, obj): def has_delete_permission(self, request, obj):
@ -47,11 +24,13 @@ class ServidorInline(admin.TabularInline):
def imagem_foto(sels, servidor): def imagem_foto(sels, servidor):
if servidor.foto: if servidor.foto:
return '<img src="{url}" style="height: 60px; width: 60px; border-radius: 50%;">'.format(url=servidor.foto.url) return mark_safe(
f'<img src="{servidor.foto.url}" style="height: 60px; '
f'width: 60px; border-radius: 50%;">'
)
else: else:
return "" return ""
imagem_foto.short_description = _("foto") imagem_foto.short_description = _("foto")
imagem_foto.allow_tags = True
def is_active(self, servidor): def is_active(self, servidor):
if servidor.user: if servidor.user:
@ -62,19 +41,19 @@ class ServidorInline(admin.TabularInline):
is_active.boolean = True is_active.boolean = True
is_active.short_description = _('ativo') is_active.short_description = _('ativo')
@admin.register(Servico) @admin.register(Servico)
class ServicoAdmin(admin.ModelAdmin): class ServicoAdmin(admin.ModelAdmin):
list_display = ['sigla', 'nome', 'subordinado', 'responsavel'] list_display = ['sigla', 'nome', 'subordinado', 'responsavel']
list_filter = [ServicoFilter,] list_filter = [ServicoFilter,]
search_fields = ['nome', 'sigla',] search_fields = ['nome', 'sigla',]
autocomplete_fields = ['subordinado', 'responsavel',]
inlines = [ServicoInline, ServidorInline,] inlines = [ServicoInline, ServidorInline,]
@admin.register(Servidor) @admin.register(Servidor)
class ServidorAdmin(BaseModelAdmin): class ServidorAdmin(admin.ModelAdmin):
list_display = ('imagem_foto', 'nome_completo', 'is_active', 'servico', ) list_display = ('imagem_foto', 'nome_completo', 'is_active', 'servico', )
list_display_links = ('imagem_foto', 'nome_completo',) list_display_links = ('imagem_foto', 'nome_completo',)
list_filter = ('user__is_active', 'externo', 'servico') list_filter = ('user__is_active', 'externo', 'servico',)
search_fields = ('nome_completo', 'user__email', 'user__first_name', search_fields = ('nome_completo', 'user__email', 'user__first_name',
'user__last_name', 'user__username', 'servico__nome', 'user__last_name', 'user__username', 'servico__nome',
'servico__sigla') 'servico__sigla')
@ -92,16 +71,6 @@ class ServidorAdmin(BaseModelAdmin):
return super(ServidorAdmin, self).lookup_allowed(lookup, value) or \ return super(ServidorAdmin, self).lookup_allowed(lookup, value) or \
lookup in ['user__is_active__exact'] lookup in ['user__is_active__exact']
# def has_add_permission(self, request):
# return False
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'foto':
request = kwargs.pop("request", None)
kwargs['widget'] = AdminImageWidget
return db_field.formfield(**kwargs)
return super(ServidorAdmin, self).formfield_for_dbfield(db_field, **kwargs)
def is_active(self, servidor): def is_active(self, servidor):
if servidor.user: if servidor.user:
return servidor.user.is_active return servidor.user.is_active
@ -113,8 +82,10 @@ class ServidorAdmin(BaseModelAdmin):
def imagem_foto(sels, servidor): def imagem_foto(sels, servidor):
if servidor.foto: if servidor.foto:
return '<img src="{url}" style="height: 60px; width: 60px; border-radius: 50%;">'.format(url=servidor.foto.url) return mark_safe(
f'<img src="{servidor.foto.url}" style="height: 60px; '
f'width: 60px; border-radius: 50%;">'
)
else: else:
return "" return ""
imagem_foto.short_description = _("foto") imagem_foto.short_description = _("foto")
imagem_foto.allow_tags = True

20
sigi/apps/servidores/filters.py

@ -0,0 +1,20 @@
from django.contrib import admin
from django.utils.translation import gettext as _
from sigi.apps.servidores.models import Servico
class ServicoFilter(admin.SimpleListFilter):
title = _("Subordinados à")
parameter_name = 'subordinado__id__exact'
def lookups(self, request, model_admin):
return ([('None', _("Nenhum"))] +
[(s.id, s.nome) for s in Servico.objects.exclude(servico=None)])
def queryset(self, request, queryset):
if self.value():
if self.value() == "None":
queryset = queryset.filter(subordinado=None)
else:
queryset = queryset.filter(subordinado__id=self.value())
return queryset

66
sigi/apps/servidores/forms.py

@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
from collections import namedtuple
from django import forms
from django.utils.translation import gettext as _
from sigi.apps.servidores.models import Ferias, Licenca, Funcao, Servidor
def valida_data_inicial_menor_que_final(data, chave_ini, chave_fim):
if data.get(chave_ini) >= data.get(chave_fim):
raise forms.ValidationError(_(
"A data de início deve ser menor que a data final. Verifique novamente"))
class FeriasForm(forms.ModelForm):
class Meta:
model = Ferias
fields = '__all__'
def clean(self):
data = self.cleaned_data
valida_data_inicial_menor_que_final(data, 'inicio_ferias', 'fim_ferias')
return data
class LicencaForm(forms.ModelForm):
class Meta:
model = Licenca
fields = '__all__'
def clean(self):
data = self.cleaned_data
valida_data_inicial_menor_que_final(data, 'inicio_licenca', 'fim_licenca')
return data
Periodo = namedtuple('Periodo', ['ini', 'fim'])
def periodos_se_sobrepoe(periodo1, periodo2):
return not (periodo1.fim < periodo2.ini or periodo2.fim < periodo1.ini)
class FuncaoForm(forms.ModelForm):
class Meta:
model = Funcao
fields = '__all__'
def clean(self):
data = self.cleaned_data
valida_data_inicial_menor_que_final(data, 'inicio_funcao', 'fim_funcao')
# Verifica na função anterior, se o seu período é igual
# ou está entre o período da função atual.
servidor = Servidor.objects.get(nome_completo=data.get('servidor'))
for funcao in servidor.funcao_set.all():
if periodos_se_sobrepoe(
Periodo(funcao.inicio_funcao, funcao.fim_funcao),
Periodo(data.get('inicio_funcao'), data.get('fim_funcao'))):
raise forms.ValidationError(_(
"Este período coincide com o de outra função exercida."))
return data

16
sigi/apps/servidores/migrations/0001_initial.py

@ -97,8 +97,8 @@ class Migration(migrations.Migration):
('apontamentos', models.TextField(null=True, verbose_name='apontamentos', blank=True)), ('apontamentos', models.TextField(null=True, verbose_name='apontamentos', blank=True)),
('email_pessoal', models.EmailField(max_length=75, null=True, verbose_name=b'email pessoal', blank=True)), ('email_pessoal', models.EmailField(max_length=75, null=True, verbose_name=b'email pessoal', blank=True)),
('ramal', models.CharField(max_length=25, null=True, blank=True)), ('ramal', models.CharField(max_length=25, null=True, blank=True)),
('servico', models.ForeignKey(blank=True, to='servidores.Servico', null=True)), ('servico', models.ForeignKey(blank=True, to='servidores.Servico', null=True, on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, unique=True)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, unique=True, on_delete=models.CASCADE)),
], ],
options={ options={
'ordering': ('nome_completo',), 'ordering': ('nome_completo',),
@ -112,7 +112,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('nome', models.CharField(max_length=250, null=True)), ('nome', models.CharField(max_length=250, null=True)),
('sigla', models.CharField(max_length=10, null=True)), ('sigla', models.CharField(max_length=10, null=True)),
('responsavel', models.ForeignKey(related_name=b'diretor', to='servidores.Servidor', null=True)), ('responsavel', models.ForeignKey(related_name=b'diretor', to='servidores.Servidor', null=True, on_delete=models.CASCADE)),
], ],
options={ options={
'ordering': ('nome',), 'ordering': ('nome',),
@ -122,31 +122,31 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='servico', model_name='servico',
name='responsavel', name='responsavel',
field=models.ForeignKey(related_name=b'chefe', to='servidores.Servidor', null=True), field=models.ForeignKey(related_name=b'chefe', to='servidores.Servidor', null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='servico', model_name='servico',
name='subsecretaria', name='subsecretaria',
field=models.ForeignKey(to='servidores.Subsecretaria', null=True), field=models.ForeignKey(to='servidores.Subsecretaria', null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='licenca', model_name='licenca',
name='servidor', name='servidor',
field=models.ForeignKey(to='servidores.Servidor'), field=models.ForeignKey(to='servidores.Servidor', on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='funcao', model_name='funcao',
name='servidor', name='servidor',
field=models.ForeignKey(to='servidores.Servidor'), field=models.ForeignKey(to='servidores.Servidor', on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='ferias', model_name='ferias',
name='servidor', name='servidor',
field=models.ForeignKey(to='servidores.Servidor'), field=models.ForeignKey(to='servidores.Servidor', on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

2
sigi/apps/servidores/migrations/0006_auto_20210429_0822.py

@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='servidor', model_name='servidor',
name='user', name='user',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True), field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

9
sigi/apps/servidores/models.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models.signals import post_save, pre_save from django.db.models.signals import post_save, pre_save
@ -27,8 +26,8 @@ class Servico(models.Model):
verbose_name = _('serviço') verbose_name = _('serviço')
verbose_name_plural = _('serviços') verbose_name_plural = _('serviços')
def __unicode__(self): def __str__(self):
return "{sigla} - {nome}".format(sigla=self.sigla, nome=self.nome) return f"{self.sigla} - {self.nome}"
class Servidor(models.Model): class Servidor(models.Model):
user = models.ForeignKey( user = models.ForeignKey(
@ -64,7 +63,7 @@ class Servidor(models.Model):
ordering = ('nome_completo',) ordering = ('nome_completo',)
verbose_name_plural = 'servidores' verbose_name_plural = 'servidores'
def __unicode__(self): def __str__(self):
return self.nome_completo return self.nome_completo
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -80,7 +79,7 @@ User.servidor = property(lambda user: Servidor.objects.get(user=user)
else None) else None)
# Sinal para ao criar um usuário criar um servidor # Sinal para ao criar um usuário criar um servidor
# baseado no nome contino no LDAP # baseado no nome contido no LDAP
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):
if created: if created:
Servidor.objects.create( Servidor.objects.create(

14
sigi/apps/servidores/templates/admin/servidores/servidor/change_list.html

@ -1,14 +0,0 @@
{% extends 'admin/change_list.html' %}
{% load i18n reporting_tags %}
{% block object-tools-items %}
<li><a href="/servidores/servidores_por_cargo.pdf" class="historylink">
<span class="glyphicon glyphicon-list-alt"></span>
{% trans 'Relatório por cargo' %}
</a></li>
<li><a href="/servidores/servidores_por_funcao.pdf" class="historylink">
<span class="glyphicon glyphicon-list-alt"></span>
{% trans 'Relatório por função' %}
</a></li>
{{ block.super }}
{% endblock %}

18
sigi/apps/servidores/templates/admin/widgets/clearable_file_input.html

@ -0,0 +1,18 @@
<a href="{{ widget.value.url }}"><img src="{{ widget.value.url }}"/></a>
{% if widget.is_initial %}
<p class="file-upload">
{{ widget.initial_text }}:
<a href="{{ widget.value.url }}">{{ widget.value }}</a>
{% if not widget.required %}
<span class="clearable-file-input">
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>
</span>
{% endif %}
<br>
{{ widget.input_text }}:
{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.is_initial %}
</p>
{% endif %}

31
sigi/apps/servidores/test_servidores_forms.py

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from django import forms
import pytest
from datetime import date
from sigi.apps.servidores.forms import valida_data_inicial_menor_que_final, Periodo, periodos_se_sobrepoe
@pytest.mark.parametrize('data', [
dict(ini=1, fim=2),
pytest.mark.xfail(raises=forms.ValidationError)(dict(ini=2, fim=1)),
pytest.mark.xfail(raises=forms.ValidationError)(dict(ini=1, fim=1)),
])
def test_valida_data_inicial_menor_que_final(data):
valida_data_inicial_menor_que_final(data, 'ini', 'fim')
periodos = [
[Periodo(date(2000, 10, 1), date(2001, 1, 1)), Periodo(date(2001, 1, 1), date(2002, 2, 2)), True], # um dia de interseção
[Periodo(date(2000, 10, 1), date(2001, 1, 1)), Periodo(date(2001, 1, 2), date(2002, 2, 2)), False], # exatamente um dia após
[Periodo(date(2000, 10, 1), date(2001, 1, 1)), Periodo(date(2000, 12, 2), date(2002, 2, 2)), True],
[Periodo(date(2000, 10, 1), date(2001, 1, 1)), Periodo(date(2014, 1, 1), date(2014, 2, 2)), False],
]
# para testar que a ordem dos parametros nao importa
periodos_trocados = [[b, a, res] for [a, b, res] in periodos]
@pytest.mark.parametrize('periodo1, periodo2, resultado', periodos + periodos_trocados)
def test_periodos_se_sobrepoe(periodo1, periodo2, resultado):
assert periodos_se_sobrepoe(periodo1, periodo2) == resultado

18
sigi/apps/utils/admin_widgets.py

@ -1,18 +0,0 @@
from django.contrib.admin.widgets import AdminFileWidget
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
class AdminImageWidget(AdminFileWidget):
def render(self, name, value, attrs=None):
output = []
if value and getattr(value, "url", None):
image_url = value.url
file_name = str(value)
output.append(
''' <a href="%s" target="_blank"><img src="%s" width="100"
height="100" alt="%s"/></a> <br/> %s''' %
(image_url, image_url, file_name, _('Change') + ':'))
output.append(super(AdminFileWidget, self).render(name, value, attrs))
return mark_safe(''.join(output))

16
sigi/settings/base.py

@ -19,13 +19,15 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'sigi.apps.servidores',
'django_bootstrap5',
'django.forms',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'sigi.apps.servidores',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -43,7 +45,7 @@ ROOT_URLCONF = 'sigi.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -56,20 +58,26 @@ TEMPLATES = [
}, },
] ]
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
WSGI_APPLICATION = 'sigi.wsgi.application' WSGI_APPLICATION = 'sigi.wsgi.application'
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/ # https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'pt-br'
TIME_ZONE = 'UTC' TIME_ZONE = "America/Sao_Paulo"
USE_I18N = True USE_I18N = True
USE_L10N = True
USE_TZ = True USE_TZ = True
USE_THOUSAND_SEPARATOR = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/ # https://docs.djangoproject.com/en/4.0/howto/static-files/

7
sigi/settings/development.py

@ -11,6 +11,13 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
INTERNAL_IPS = ["127.0.0.1",]
# Application definition
INSTALLED_APPS = ['debug_toolbar',] + INSTALLED_APPS
MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware',] + MIDDLEWARE
# Database # Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases # https://docs.djangoproject.com/en/4.0/ref/settings/#databases

0
templates/base_change_form.html → sigi/templates/base_change_form.html

8
sigi/urls.py

@ -14,8 +14,14 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path, include
from django.conf import settings
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
] ]
if settings.DEBUG:
urlpatterns = urlpatterns + [
path('__debug__/', include('debug_toolbar.urls'))
]

Loading…
Cancel
Save