class Job(TimeStampedModelMixin): """ Model for a Job. """ def __init__(self, *args, **kwargs): kwargs['the_geom'] = convert_polygon(kwargs.get('the_geom')) or '' kwargs['the_geom_webmercator'] = convert_polygon( kwargs.get('the_geom_webmercator')) or '' kwargs['the_geog'] = convert_polygon(kwargs.get('the_geog')) or '' super(Job, self).__init__(*args, **kwargs) id = models.AutoField(primary_key=True, editable=False) uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False, db_index=True) user = models.ForeignKey(User, related_name='owner') name = models.CharField(max_length=100, db_index=True) description = models.CharField(max_length=1000, db_index=True) event = models.CharField(max_length=100, db_index=True, default='', blank=True) region = models.ForeignKey(Region, null=True, on_delete=models.SET_NULL) provider_tasks = models.ManyToManyField(ProviderTask, related_name='provider_tasks') configs = models.ManyToManyField(ExportConfig, related_name='configs', blank=True) published = models.BooleanField(default=False, db_index=True) # publish export feature_save = models.BooleanField( default=False, db_index=True) # save feature selections feature_pub = models.BooleanField( default=False, db_index=True) # publish feature selections the_geom = models.MultiPolygonField(verbose_name='Extent for export', srid=4326, default='') the_geom_webmercator = models.MultiPolygonField( verbose_name='Mercator extent for export', srid=3857, default='') the_geog = models.MultiPolygonField( verbose_name='Geographic extent for export', geography=True, default='') objects = models.GeoManager() include_zipfile = models.BooleanField(default=False) json_tags = JSONField(default=dict) class Meta: # pragma: no cover managed = True db_table = 'jobs' def save(self, *args, **kwargs): self.the_geom = convert_polygon(self.the_geom) self.the_geog = GEOSGeometry(self.the_geom) self.the_geom_webmercator = self.the_geom.transform(ct=3857, clone=True) super(Job, self).save(*args, **kwargs) def __str__(self): return '{0}'.format(self.name) @property def overpass_extents(self, ): """ Return the export extents in order required by Overpass API. """ extents = GEOSGeometry(self.the_geom).extent # (w,s,e,n) # overpass needs extents in order (s,w,n,e) overpass_extents = '{0},{1},{2},{3}'.format(str(extents[1]), str(extents[0]), str(extents[3]), str(extents[2])) return overpass_extents @property def extents(self, ): return GEOSGeometry(self.the_geom).extent # (w,s,e,n) @property def filters(self, ): """ Return key=value pairs for each tag in this export. Used in utils.overpass.filter to filter the export. """ # Command-line key=value filters for osmfilter filters = [] for tag in self.json_tags: kv = '{0}={1}'.format(tag['key'], tag['value']) filters.append(kv) return filters @property def categorised_tags(self, ): """ Return tags mapped according to their geometry types. """ points = set() lines = set() polygons = set() for tag in self.json_tags: if 'point' in tag['geom']: points.add(tag['key']) if 'line' in tag['geom']: lines.add(tag['key']) if 'polygon' in tag['geom']: polygons.add(tag['key']) return { 'points': sorted(list(points)), 'lines': sorted(list(lines)), 'polygons': sorted(list(polygons)) } @property def bounds_geojson(self, ): return serialize('geojson', [self], geometry_field='the_geom', fields=('name', 'the_geom'))
class JustificativaAusencia(models.Model): FIELDFILE_NAME = ('upload_anexo', ) metadata = JSONField(verbose_name=_('Metadados'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) TIPO_AUSENCIA_CHOICES = Choices( (1, 'materia', 'Matéria'), (2, 'sessao', 'Sessão'), ) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT, verbose_name=_('Parlamentar')) sessao_plenaria = models.ForeignKey(SessaoPlenaria, on_delete=models.CASCADE, verbose_name=_('Sessão Plenária')) tipo_ausencia = models.ForeignKey(TipoJustificativa, on_delete=models.PROTECT, verbose_name=_('Tipo de Justificativa')) data = models.DateField(verbose_name=_('Data')) hora = models.CharField(max_length=5, verbose_name=_('Horário (hh:mm)')) observacao = models.TextField(max_length=150, blank=True, verbose_name=_('Observação')) ausencia = models.PositiveIntegerField(verbose_name=_('Ausente em'), choices=TIPO_AUSENCIA_CHOICES, default=1) materias_do_expediente = models.ManyToManyField( ExpedienteMateria, blank=True, verbose_name=_('Matérias do Expediente')) materias_da_ordem_do_dia = models.ManyToManyField( OrdemDia, blank=True, verbose_name=_('Matérias do Ordem do Dia')) upload_anexo = models.FileField(blank=True, null=True, storage=OverwriteStorage(), upload_to=anexo_upload_path, verbose_name=_('Anexo de Justificativa')) class Meta: verbose_name = _('Justificativa de Ausência') verbose_name_plural = _('Justificativas de Ausências') def __str__(self): return 'Justificativa de Ausência' def delete(self, using=None, keep_parents=False): if self.upload_anexo: self.upload_anexo.delete() return models.Model.delete(self, using=using, keep_parents=keep_parents) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and self.upload_anexo: upload_anexo = self.upload_anexo self.upload_anexo = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.upload_anexo = upload_anexo return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
class Migration(migrations.Migration): dependencies = [ ('occasions', '0004_character'), ] operations = [ migrations.CreateModel( name='Meet', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', model_utils.fields.AutoCreatedField( default=django.utils.timezone.now, editable=False, verbose_name='created')), ('modified', model_utils.fields.AutoLastModifiedField( default=django.utils.timezone.now, editable=False, verbose_name='modified')), ('audience_editable', models.BooleanField( default=True, help_text= '[some meets are editable only by coworkers] participant can edit AttendingMeet?', verbose_name='participant can edit AttendingMeet?')), ('shown_audience', models.BooleanField( default=True, db_index=True, help_text= '[some meets are only for internal records] show the AttendingMeet to attendee?', verbose_name='show AttendingMeet to participant?')), ('start', models.DateTimeField(null=False, blank=False, default=Utility.now_with_timezone)), ('finish', models.DateTimeField( null=False, blank=False, help_text="Required for user to filter by time")), ('is_removed', models.BooleanField(default=False)), ('site_type', models.ForeignKey( help_text='site: django_content_type id for table name', on_delete=models.SET(0), to='contenttypes.ContentType')), ('site_id', models.BigIntegerField()), ('assembly', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='occasions.Assembly')), ('slug', models.SlugField(max_length=50, unique=True)), ('display_name', models.CharField( blank=True, null=True, db_index=True, help_text= 'The Rock, Little Foot, singspiration, A/V control, etc.', max_length=50)), ('infos', JSONField( blank=True, default=dict, help_text= 'Example: {"info": "...", "url": "https://..."}. Please keep {} here even no data', null=True)), ], options={ 'db_table': 'occasions_meets', }, bases=(models.Model, Utility), ), ]
class Attendee(UUIDModel, Utility, TimeStampedModel, SoftDeletableModel): # RELATIVES_KEYWORDS = ['parent', 'mother', 'guardian', 'father', 'caregiver'] # AS_PARENT_KEYWORDS = ['notifier', 'caregiver'] # to find attendee's parents/caregiver in cowokers view of all activities # BE_LISTED_KEYWORDS = ['care receiver'] # let the attendee's attendance showed in their parent/caregiver account pasts = GenericRelation('persons.Past') places = GenericRelation('whereabouts.Place') notes = GenericRelation(Note) related_ones = models.ManyToManyField('self', through='Relationship', symmetrical=False, related_name='related_to') division = models.ForeignKey('whereabouts.Division', default=0, null=False, blank=False, on_delete=models.SET(0)) user = models.OneToOneField(settings.AUTH_USER_MODEL, default=None, null=True, blank=True, on_delete=models.SET_NULL) families = models.ManyToManyField('persons.Family', through='FamilyAttendee', related_name='families') first_name = models.CharField(max_length=25, db_index=True, null=True, blank=True) last_name = models.CharField(max_length=25, db_index=True, null=True, blank=True) first_name2 = models.CharField(max_length=12, db_index=True, null=True, blank=True) last_name2 = models.CharField(max_length=8, db_index=True, null=True, blank=True) gender = models.CharField(max_length=11, blank=False, null=False, default=GenderEnum.UNSPECIFIED, choices=GenderEnum.choices()) actual_birthday = models.DateField(blank=True, null=True) estimated_birthday = models.DateField(blank=True, null=True) deathday = models.DateField(blank=True, null=True) photo = PrivateFileField( "Photo", blank=True, null=True, upload_to="attendee_portrait" ) #https://github.com/edoburu/django-private-storage progressions = JSONField( null=True, blank=True, default=dict, help_text= 'Example: {"Christian": true, "baptized": {"time": "12/31/2020", "place":"SF"}}. Please keep {} here even no data' ) infos = JSONField( null=True, blank=True, default=Utility.attendee_infos, help_text= 'Example: {"fixed": {"food_pref": "peanut allergy", "nick_name": "John"}}. Please keep {} here even no data' ) @property def display_label(self): return (self.first_name or '') + ' ' + (self.last_name or '') + ' ' + ( self.last_name2 or '') + (self.first_name2 or '') @property def division_label(self): return self.division.display_name if self.division else None @cached_property def family_members(self): return self.__class__.objects.filter( families__in=self.families.all()).distinct() @cached_property def self_phone_numbers(self): return self.self_addresses_for_fields_of(['phone1', 'phone2']) @cached_property def self_email_addresses(self): return self.self_addresses_for_fields_of(['email1', 'email2']) def self_addresses_for_fields_of(self, fields): contacts = self.infos.get('contacts', {}) return ', '.join( [contacts.get(field) for field in fields if contacts.get(field)]) @cached_property def caregiver_email_addresses(self): return self.caregiver_addresses_for_fields_of(['email1', 'email2']) @cached_property def caregiver_phone_numbers(self): return self.caregiver_addresses_for_fields_of(['phone1', 'phone2']) def caregiver_addresses_for_fields_of(self, fields): return ', '.join( set( a.self_addresses_for_fields_of(fields) for a in self.get_relative_emergency_contacts())) def get_relative_emergency_contacts(self): return self.related_ones.filter( to_attendee__relation__relative=True, to_attendee__relation__emergency_contact=True, to_attendee__finish__gte=datetime.now(timezone.utc), ) def under_same_org_with(self, other_attendee_id): if other_attendee_id: return Attendee.objects.filter( pk=other_attendee_id, division__organization=self.division.organization).exists() return False @cached_property def parents_notifiers_names(self): """ :return: attendees' names of their parents/caregiviers """ return ', '.join( list(self.get_relative_emergency_contacts().order_by( '-to_attendee__relation__display_order', ).values_list( 'infos__names__original', flat=True))) def age(self): birthday = self.actual_birthday or self.estimated_birthday try: if birthday: return (date.today() - birthday) // timedelta(days=365.2425) else: return None except Exception as e: print(self.__str__() + "'s birthday incorrect: ", birthday, '. Type: ', type(birthday), ' exception: ', e) return None def __str__(self): return self.display_label # def all_relations(self): #cannot import Relationship, probably needs native query # return dict(((r.from_attendee, r.relation) if r.to_attendee == self else (r.to_attendee, r.relation) for r in Relationship.objects.filter(Q(from_attendee=self.id) | Q(to_attendee=self.id)))) # switching to symmetrical False with Facebook model (but add relationship both ways and need add/remove_relationship methods) http://charlesleifer.com/blog/self-referencing-many-many-through/ # also attendee.related_ones will return deleted relationship, so extra filter is required (.filter(relationship__is_removed = False)) def clean(self): if not (self.last_name or self.last_name2): raise ValidationError("You must specify a last_name") def get_absolute_url(self): return reverse('/persons/attendee_detail_view/', kwargs={'pk': self.pk}) class Meta: db_table = 'persons_attendees' ordering = ['last_name', 'first_name'] indexes = [ GinIndex( fields=['infos'], name='attendee_infos_gin', ), GinIndex( fields=['progressions'], name='attendee_progressions_gin', ), ] def save(self, *args, **kwargs): name = f"{self.first_name or ''} {self.last_name or ''}".strip() name2 = f"{self.last_name2 or ''}{self.first_name2 or ''}".strip() self.infos['names']['original'] = f"{name} {name2}".strip() if self.division.organization.infos.get( 'flags', {}).get('opencc_convert'): # Let search work in either language self.infos['names']['traditional'] = OpenCC('s2t').convert(name2) self.infos['names']['simplified'] = OpenCC('t2s').convert(name2) # if self.division.organization.infos.flags.accent_convert: # For Spanish, Not in V1 # self.infos['names']['unaccented'] = converter(name) super(Attendee, self).save(*args, **kwargs)
class Item(models.Model): item_type = models.ForeignKey(ItemType, on_delete=models.CASCADE, blank=False) data = JSONField("data", default=defaultJSON, blank=False)
class PhysicalArtifactFormDataRecord(models.Model): INSTANCE_ID_SEPARATOR = "__instance-" ACTION_TYPE_ASSIGN_VALUE = 'value' ACTION_TYPE_ASSIGN_COMMENT = 'comment' COMPONENT_TYPE_TEXT = 'text' COMPONENT_TYPE_TAB = 'tab' COMPONENT_TYPE_SECTION = 'section' COMPONENT_TYPE_GROUP = 'group' COMPONENT_TYPE_NUMBER = 'number' COMPONENT_TYPE_EMAIL = 'email' COMPONENT_TYPE_SELECT = 'select' COMPONENT_TYPE_MULTI_SELECT = 'multi-select' COMPONENT_TYPE_TEXT_AREA = 'text_area' COMPONENT_TYPE_TABLE = 'table' COMPONENT_TYPE_EXPANDER_TABLE = 'expander_table' COMPONENT_TYPE_LABEL = 'label' COMPONENT_TYPE_RADIO = 'radiobuttons' COMPONENT_TYPE_CHECKBOX = 'checkbox' COMPONENT_TYPE_DECLARATION = 'declaration' COMPONENT_TYPE_FILE = 'file' COMPONENT_TYPE_DATE = 'date' COMPONENT_TYPE_CHOICES = ( (COMPONENT_TYPE_TEXT, 'Text'), (COMPONENT_TYPE_TAB, 'Tab'), (COMPONENT_TYPE_SECTION, 'Section'), (COMPONENT_TYPE_GROUP, 'Group'), (COMPONENT_TYPE_NUMBER, 'Number'), (COMPONENT_TYPE_EMAIL, 'Email'), (COMPONENT_TYPE_SELECT, 'Select'), (COMPONENT_TYPE_MULTI_SELECT, 'Multi-Select'), (COMPONENT_TYPE_TEXT_AREA, 'Text Area'), (COMPONENT_TYPE_TABLE, 'Table'), (COMPONENT_TYPE_EXPANDER_TABLE, 'Expander Table'), (COMPONENT_TYPE_LABEL, 'Label'), (COMPONENT_TYPE_RADIO, 'Radio'), (COMPONENT_TYPE_CHECKBOX, 'Checkbox'), (COMPONENT_TYPE_DECLARATION, 'Declaration'), (COMPONENT_TYPE_FILE, 'File'), (COMPONENT_TYPE_DATE, 'Date'), ) physical_artifact = models.ForeignKey(PhysicalArtifact, related_name='form_data_records') field_name = models.CharField(max_length=512, blank=True, null=True) schema_name = models.CharField(max_length=256, blank=True, null=True) instance_name = models.CharField(max_length=256, blank=True, null=True) component_type = models.CharField(max_length=64, choices=COMPONENT_TYPE_CHOICES, default=COMPONENT_TYPE_TEXT) value = JSONField(blank=True, null=True) comment = models.TextField(blank=True, null=True) deficiency = models.TextField(blank=True, null=True) def __str__(self): return "Physical Artifact {id} record {field}: {value}".format( id=self.physical_artifact_id, field=self.field_name, value=self.value[:8]) class Meta: app_label = 'wildlifecompliance' unique_together = ( 'physical_artifact', 'field_name', ) @staticmethod #def process_form(request, PhysicalArtifact, form_data, action=ACTION_TYPE_ASSIGN_VALUE): def process_form(PhysicalArtifact, form_data, action=ACTION_TYPE_ASSIGN_VALUE): #can_edit_comments = request.user.has_perm( # 'wildlifecompliance.licensing_officer' #) or request.user.has_perm( # 'wildlifecompliance.assessor' #) #can_edit_deficiencies = request.user.has_perm( # 'wildlifecompliance.licensing_officer' #) if action == PhysicalArtifactFormDataRecord.ACTION_TYPE_ASSIGN_COMMENT and\ not can_edit_comments and not can_edit_deficiencies: raise Exception('You are not authorised to perform this action!') for field_name, field_data in form_data.items(): schema_name = field_data.get('schema_name', '') component_type = field_data.get('component_type', '') value = field_data.get('value', '') comment = field_data.get('comment_value', '') deficiency = field_data.get('deficiency_value', '') instance_name = '' if PhysicalArtifactFormDataRecord.INSTANCE_ID_SEPARATOR in field_name: [parsed_schema_name, instance_name] = field_name.split( PhysicalArtifactFormDataRecord.INSTANCE_ID_SEPARATOR) schema_name = schema_name if schema_name else parsed_schema_name form_data_record, created = PhysicalArtifactFormDataRecord.objects.get_or_create( physical_artifact_id=PhysicalArtifact.id, field_name=field_name) if created: form_data_record.schema_name = schema_name form_data_record.instance_name = instance_name form_data_record.component_type = component_type if action == PhysicalArtifactFormDataRecord.ACTION_TYPE_ASSIGN_VALUE: form_data_record.value = value elif action == PhysicalArtifactFormDataRecord.ACTION_TYPE_ASSIGN_COMMENT: if can_edit_comments: form_data_record.comment = comment if can_edit_deficiencies: form_data_record.deficiency = deficiency form_data_record.save()
class WildlifeLicence(models.Model): STATUS_CHOICES = ( ('current','Current'), ('expired','Expired'), ('cancelled','Cancelled'), ('surrendered','Surrendered'), ('suspended','Suspended') ) status = models.CharField(max_length=40, choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0]) parent_licence=models.ForeignKey('self',blank=True,null=True,related_name='children_licence') licence_document = models.ForeignKey(LicenceDocument, blank=True, null=True, related_name='licence_document') cover_letter_document = models.ForeignKey(LicenceDocument, blank=True, null=True, related_name='cover_letter_document') replaced_by = models.ForeignKey('self', blank=True, null=True) current_application = models.ForeignKey(Application,related_name = '+') activity = models.CharField(max_length=255, blank=True, null=True) region = models.CharField(max_length=255, blank=True, null=True) tenure = models.CharField(max_length=255, blank=True, null=True) title = models.CharField(max_length=255, blank=True, null=True) renewal_sent = models.BooleanField(default=False) issue_date = models.DateField(blank=True,null=True) original_issue_date = models.DateField(auto_now_add=True) start_date = models.DateField(blank=True,null=True) expiry_date = models.DateField(blank=True,null=True) surrender_details = JSONField(blank=True,null=True) suspension_details = JSONField(blank=True,null=True) # applicant = models.ForeignKey(Organisation,on_delete=models.PROTECT, related_name='wildlifecompliance_licences') org_applicant = models.ForeignKey(Organisation, blank=True, null=True, related_name='wildlifecompliance_org_applicant') proxy_applicant = models.ForeignKey(EmailUser, blank=True, null=True, related_name='wildlifecompliance_proxy_applicant') submitter = models.ForeignKey(EmailUser, blank=True, null=True, related_name='wildlifecompliance_submitter') extracted_fields = JSONField(blank=True, null=True) licence_activity_type=models.ForeignKey('WildlifeLicenceActivityType',null=True) licence_type=models.ForeignKey('WildlifeLicenceActivity',null=True) licence_number = models.CharField(max_length=64, blank=True, null=True) licence_sequence = models.IntegerField(blank=True, default=1) licence_class = models.ForeignKey(WildlifeLicenceClass) #licence_sequence = models.IntegerField(blank=True, unique=True, default=seq_idx) # licence_activity_type = models.ForeignKey(WildlifeLicenceActivityType) # licence_descriptor = models.ForeignKey(WildlifeLicenceDescriptor) class Meta: unique_together = (('licence_number','licence_sequence','licence_class')) app_label = 'wildlifecompliance' def __str__(self): return '{} {}-{}'.format(self.licence_type, self.licence_number, self.licence_sequence) def save(self, *args, **kwargs): super(WildlifeLicence, self).save(*args,**kwargs) if not self.licence_number: self.licence_number = 'L{0:06d}'.format(self.next_licence_number_id) #self.licence_number = 'L{0:06d}'.format(self.pk) self.save() @property def next_licence_number_id(self): licence_number_max = WildlifeLicence.objects.all().aggregate(Max('licence_number'))['licence_number__max'] if licence_number_max == None: return self.pk else: return int(licence_number_max.split('L')[1]) + 1 # def seq_idx(): # no = WildlifeLicence.objects.get().aggregate(Max(order)) # if no == None: # return 1 # else: # return no + 1 @property def reference(self): return '{}-{}'.format(self.licence_number, self.licence_sequence) @property def is_issued(self): return self.licence_number is not None and len(self.licence_number) > 0 def generate_doc(self): from wildlifecompliance.components.licences.pdf import create_licence_doc self.licence_document = create_licence_doc(self,self.current_application) self.save() def log_user_action(self, action, request): return LicenceUserAction.log_action(self, action, request.user)
class MensagemSolicitacao(models.Model): FIELDFILE_NAME = ('anexo', ) metadata = JSONField( verbose_name=_('Metadados'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) created = models.DateTimeField( verbose_name=_('created'), editable=False, auto_now_add=True) owner = models.ForeignKey( get_settings_auth_user_model(), blank=True, null=True, default=None, verbose_name=_('owner'), related_name='+', on_delete=PROTECT) descricao = models.TextField( default='', verbose_name=_('Descrição')) solicitacao = models.ForeignKey( Solicitacao, verbose_name=_('Solicitação'), related_name='mensagemsolicitacao_set', on_delete=PROTECT) notificacoes = GenericRelation( Notificacao, related_query_name='notificacoes') anexo = models.FileField( blank=True, null=True, storage=media_protected_storage, upload_to=anexo_ouvidoria_path, verbose_name=_('Anexo'), validators=[restringe_tipos_de_arquivo_midias], help_text=_('Envie um arquivo em anexo a sua mensagem.')) content_type = models.CharField( max_length=250, default='') class Meta: ordering = ('created', ) verbose_name = _('Mensagem de Solicitação') verbose_name_plural = _('Mensagens de Solicitação') def delete(self, using=None, keep_parents=False): if self.anexo: self.anexo.delete() return models.Model.delete( self, using=using, keep_parents=keep_parents) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and self.anexo: anexo = self.anexo self.anexo = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.anexo = anexo self.content_type = self.anexo.file.content_type if self.anexo.file.content_type in TIPOS_MIDIAS_PERMITIDOS: name_file = 'anexo.%s' % TIPOS_MIDIAS_PERMITIDOS[self.content_type] self.anexo.save(name_file, self.anexo.file) models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) @property def email_notify(self): return { 'subject': self.solicitacao.email_notify['subject'], } """'body': ('Solicitação: ' + self.solicitacao.titulo, self.descricao), 'owner': self.owner, 'created': self.created""" def __str__(self): return self.descricao
class Approval(RevisionedMixin): STATUS_CHOICES = (('current', 'Current'), ('expired', 'Expired'), ('cancelled', 'Cancelled'), ('surrendered', 'Surrendered'), ('suspended', 'Suspended')) lodgement_number = models.CharField(max_length=9, blank=True, default='') status = models.CharField(max_length=40, choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0]) licence_document = models.ForeignKey(ApprovalDocument, blank=True, null=True, related_name='licence_document') cover_letter_document = models.ForeignKey( ApprovalDocument, blank=True, null=True, related_name='cover_letter_document') replaced_by = models.ForeignKey('self', blank=True, null=True) #current_proposal = models.ForeignKey(Proposal,related_name = '+') current_proposal = models.ForeignKey(Proposal, related_name='approvals') # activity = models.CharField(max_length=255) # region = models.CharField(max_length=255) # tenure = models.CharField(max_length=255,null=True) # title = models.CharField(max_length=255) renewal_document = models.ForeignKey(ApprovalDocument, blank=True, null=True, related_name='renewal_document') renewal_sent = models.BooleanField(default=False) issue_date = models.DateTimeField() original_issue_date = models.DateField(auto_now_add=True) start_date = models.DateField() expiry_date = models.DateField() surrender_details = JSONField(blank=True, null=True) suspension_details = JSONField(blank=True, null=True) applicant = models.ForeignKey(Organisation, on_delete=models.PROTECT, related_name='disturbance_approvals') extracted_fields = JSONField(blank=True, null=True) cancellation_details = models.TextField(blank=True) cancellation_date = models.DateField(blank=True, null=True) set_to_cancel = models.BooleanField(default=False) set_to_suspend = models.BooleanField(default=False) set_to_surrender = models.BooleanField(default=False) reissued = models.BooleanField(default=False) class Meta: app_label = 'disturbance' unique_together = ('lodgement_number', 'issue_date') @property def region(self): return self.current_proposal.region.name @property def district(self): return self.current_proposal.district.name @property def tenure(self): return self.current_proposal.tenure.name @property def activity(self): return self.current_proposal.activity @property def title(self): return self.current_proposal.title @property def next_id(self): #ids = map(int,[(i.lodgement_number.split('A')[1]) for i in Approval.objects.all()]) ids = map(int, [ i.split('A')[1] for i in Approval.objects.all().values_list('lodgement_number', flat=True) if i ]) return max(ids) + 1 if ids else 1 def save(self, *args, **kwargs): super(Approval, self).save(*args, **kwargs) if self.lodgement_number == '': self.lodgement_number = 'A{0:06d}'.format(self.next_id) self.save() def __str__(self): return self.lodgement_number @property def reference(self): return 'A{}'.format(self.id) @property def can_reissue(self): return self.status == 'current' or self.status == 'suspended' @property def can_reinstate(self): return (self.status == 'cancelled' or self.status == 'suspended' or self.status == 'surrendered') and self.can_action @property def allowed_assessors(self): return self.current_proposal.compliance_assessors @property def allowed_approvers(self): return self.current_proposal.allowed_approvers @property def is_issued(self): return self.licence_number is not None and len(self.licence_number) > 0 @property def can_action(self): if not (self.set_to_cancel or self.set_to_suspend or self.set_to_surrender): return True else: return False @property def can_renew(self): try: renew_conditions = { 'previous_application': self.current_proposal, 'proposal_type': 'renewal' } proposal = Proposal.objects.get(**renew_conditions) if proposal: return False except Proposal.DoesNotExist: return True @property def can_amend(self): try: amend_conditions = { 'previous_application': self.current_proposal, 'proposal_type': 'amendment' } proposal = Proposal.objects.get(**amend_conditions) if proposal: return False except Proposal.DoesNotExist: if self.can_renew: return True else: return False def generate_doc(self, user, preview=False): from disturbance.components.approvals.pdf import create_approval_doc, create_approval_pdf_bytes copied_to_permit = self.copiedToPermit_fields( self.current_proposal) #Get data related to isCopiedToPermit tag if preview: return create_approval_pdf_bytes(self, self.current_proposal, copied_to_permit, user) self.licence_document = create_approval_doc(self, self.current_proposal, copied_to_permit, user) self.save(version_comment='Created Approval PDF: {}'.format( self.licence_document.name)) self.current_proposal.save(version_comment='Created Approval PDF: {}'. format(self.licence_document.name)) def generate_renewal_doc(self): from disturbance.components.approvals.pdf import create_renewal_doc self.renewal_document = create_renewal_doc(self, self.current_proposal) self.save(version_comment='Created Approval PDF: {}'.format( self.renewal_document.name)) self.current_proposal.save(version_comment='Created Approval PDF: {}'. format(self.renewal_document.name)) def copiedToPermit_fields(self, proposal): p = proposal copied_data = [] search_assessor_data = [] search_schema = search_multiple_keys(p.schema, primary_search='isCopiedToPermit', search_list=['label', 'name']) if p.assessor_data: search_assessor_data = search_keys( p.assessor_data, search_list=['assessor', 'name']) if search_schema: for c in search_schema: try: if search_assessor_data: for d in search_assessor_data: if c['name'] == d['name']: if d['assessor']: #copied_data.append({c['label'], d['assessor']}) copied_data.append( {c['label']: d['assessor']}) except: raise return copied_data def log_user_action(self, action, request): return ApprovalUserAction.log_action(self, action, request.user) def expire_approval(self, user): with transaction.atomic(): try: today = timezone.localtime(timezone.now()).date() if self.status == 'current' and self.expiry_date < today: self.status = 'expired' self.save() send_approval_expire_email_notification(self) proposal = self.current_proposal ApprovalUserAction.log_action( self, ApprovalUserAction.ACTION_EXPIRE_APPROVAL.format( self.id), user) ProposalUserAction.log_action( proposal, ProposalUserAction.ACTION_EXPIRED_APPROVAL_.format( proposal.id), user) except: raise def approval_cancellation(self, request, details): with transaction.atomic(): try: if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to cancel this approval') if not self.can_reissue and self.can_action: raise ValidationError( 'You cannot cancel approval if it is not current or suspended' ) self.cancellation_date = details.get( 'cancellation_date').strftime('%Y-%m-%d') self.cancellation_details = details.get('cancellation_details') cancellation_date = datetime.datetime.strptime( self.cancellation_date, '%Y-%m-%d') cancellation_date = cancellation_date.date() self.cancellation_date = cancellation_date today = timezone.now().date() if cancellation_date <= today: if not self.status == 'cancelled': self.status = 'cancelled' self.set_to_cancel = False send_approval_cancel_email_notification(self) else: self.set_to_cancel = True send_approval_cancel_email_notification(self, future_cancel=True) #import ipdb; ipdb.set_trace() self.save() # Log proposal action self.log_user_action( ApprovalUserAction.ACTION_CANCEL_APPROVAL.format(self.id), request) # Log entry for organisation #self.current_proposal.log_user_action(ProposalUserAction.ACTION_CANCEL_APPROVAL.format(self.current_proposal.id),request) except: raise def approval_suspension(self, request, details): with transaction.atomic(): try: if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to suspend this approval') if not self.can_reissue and self.can_action: raise ValidationError( 'You cannot suspend approval if it is not current or suspended' ) if details.get('to_date'): to_date = details.get('to_date').strftime('%d/%m/%Y') else: to_date = '' self.suspension_details = { 'from_date': details.get('from_date').strftime('%d/%m/%Y'), 'to_date': to_date, 'details': details.get('suspension_details'), } today = timezone.now().date() from_date = datetime.datetime.strptime( self.suspension_details['from_date'], '%d/%m/%Y') from_date = from_date.date() if from_date <= today: if not self.status == 'suspended': self.status = 'suspended' self.set_to_suspend = False self.save() send_approval_suspend_email_notification(self) else: self.set_to_suspend = True send_approval_suspend_email_notification( self, future_suspend=True) self.save() # Log approval action self.log_user_action( ApprovalUserAction.ACTION_SUSPEND_APPROVAL.format(self.id), request) # Log entry for proposal #self.current_proposal.log_user_action(ProposalUserAction.ACTION_SUSPEND_APPROVAL.format(self.current_proposal.id),request) except: raise def reinstate_approval(self, request): with transaction.atomic(): try: if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to reinstate this approval') if not self.can_reinstate: #if not self.status == 'suspended': raise ValidationError( 'You cannot reinstate approval at this stage') today = timezone.now().date() if not self.can_reinstate and self.expiry_date >= today: #if not self.status == 'suspended' and self.expiry_date >= today: raise ValidationError( 'You cannot reinstate approval at this stage') if self.status == 'cancelled': self.cancellation_details = '' self.cancellation_date = None if self.status == 'surrendered': self.surrender_details = {} if self.status == 'suspended': self.suspension_details = {} self.status = 'current' #self.suspension_details = {} self.save() send_approval_reinstate_email_notification(self, request) # Log approval action self.log_user_action( ApprovalUserAction.ACTION_REINSTATE_APPROVAL.format( self.id), request) # Log entry for proposal #self.current_proposal.log_user_action(ProposalUserAction.ACTION_REINSTATE_APPROVAL.format(self.current_proposal.id),request) except: raise def approval_surrender(self, request, details): with transaction.atomic(): try: if not request.user.disturbance_organisations.filter( organisation_id=self.applicant.id): if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to surrender this approval' ) if not self.can_reissue and self.can_action: raise ValidationError( 'You cannot surrender approval if it is not current or suspended' ) self.surrender_details = { 'surrender_date': details.get('surrender_date').strftime('%d/%m/%Y'), 'details': details.get('surrender_details'), } today = timezone.now().date() surrender_date = datetime.datetime.strptime( self.surrender_details['surrender_date'], '%d/%m/%Y') surrender_date = surrender_date.date() if surrender_date <= today: if not self.status == 'surrendered': self.status = 'surrendered' self.set_to_surrender = False self.save() send_approval_surrender_email_notification(self) else: self.set_to_surrender = True send_approval_surrender_email_notification( self, future_surrender=True) self.save() # Log approval action self.log_user_action( ApprovalUserAction.ACTION_SURRENDER_APPROVAL.format( self.id), request) # Log entry for proposal #self.current_proposal.log_user_action(ProposalUserAction.ACTION_SURRENDER_APPROVAL.format(self.current_proposal.id),request) except: raise def pdf_view_log(self, request): self.log_user_action( ApprovalUserAction.ACTION_APPROVAL_PDF_VIEW.format(self.id), request) return self
class Institute(models.Model): institute_id = models.AutoField(primary_key=True, editable=False) code = models.CharField(max_length=20, db_index=True, unique=True) name = models.CharField(max_length=255, db_index=True) data = JSONField() class Meta: db_table = 'vavilov_institute' def __str__(self): return '{}({})'.format(self.name, self.code) @property def num_accessions(self): return Accession.objects.filter(institute=self).count() @property def num_accessionsets(self): return AccessionSet.objects.filter(institute=self).count() def stats_by_country(self, user=None): stats = {} accession_stats = Country.objects.raw( get_institute_stats_raw_sql(institute_id=self.institute_id, stats_type='country', entity_type='accession', user=user)) accessionset_stats = Country.objects.raw( get_institute_stats_raw_sql(institute_id=self.institute_id, stats_type='country', entity_type='accessionset', user=user)) for row in accession_stats: _integrate_country_stats(stats, row, 'accession') for row in accessionset_stats: _integrate_country_stats(stats, row, 'accessionset') return sorted(stats.values(), key=itemgetter('num_accessions'), reverse=True) def stats_by_taxa(self, user=None): stats = {} accession_stats = Taxon.objects.raw( get_institute_stats_raw_sql(institute_id=self.institute_id, stats_type='taxa', entity_type='accession', user=user)) accessionset_stats = Taxon.objects.raw( get_institute_stats_raw_sql(institute_id=self.institute_id, stats_type='taxa', entity_type='accessionset', user=user)) for row in accession_stats: _integrate_taxa_stats(stats, row, 'accession') for row in accessionset_stats: _integrate_taxa_stats(stats, row, 'accessionset') return stats @property def pdcis(self): pdcis = [float(p['pdci']) for p in Passport.objects.filter(institute=self).values('pdci')] return pdci_distrib(pdcis)
class Payment(models.Model): """ Payment model. State `STATUS_PREAUTHORIZED_UNPAID` is a special state, where a future payment has been authorized and saved at the payment proveider, and only needs a special API call to confirm and "cash in" the payment. This is used for postponed subscriptions, that are created while a current subscription is still running. """ TYPE_CHOICES = ( (PAYMENT_TYPE_DIRECT_DEBIT, _('Direct Debit (SEPA)')), (PAYMENT_TYPE_CREDIT_CARD, _('Credit Card')), (PAYMENT_TYPE_PAYPAL, _('PayPal')), ) STATUS_NOT_STARTED = 0 STATUS_STARTED = 1 STATUS_COMPLETED_BUT_UNCONFIRMED = 2 STATUS_PAID = 3 STATUS_PREAUTHORIZED_UNPAID = 301 STATUS_FAILED = 101 STATUS_RETRACTED = 102 STATUS_CANCELED = 103 STATUS_CHOICES = ( (STATUS_NOT_STARTED, _('Not started')), (STATUS_STARTED, _('Payment initiated but not completed by user yet')), (STATUS_COMPLETED_BUT_UNCONFIRMED, _('Payment processing')), (STATUS_PAID, _('Successfully paid')), (STATUS_PREAUTHORIZED_UNPAID, _('Pre-authorized for payment, but not yet paid')), (STATUS_FAILED, _('Failed')), (STATUS_RETRACTED, _('Retracted')), (STATUS_CANCELED, _('Canceled')), ) # payments with this status are "done", signalling that # further payments may be made on a subscription FINALIZED_STATUSES = ( STATUS_FAILED, STATUS_PAID, ) user = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, related_name='payments', on_delete=models.CASCADE, null=True) subscription = models.ForeignKey('wechange_payments.Subscription', editable=False, related_name='payments', on_delete=models.SET_NULL, blank=True, null=True) vendor_transaction_id = models.CharField(_('Vendor Transaction Id'), max_length=50, help_text='An Id for the payment generated by the Payment Service') internal_transaction_id = models.CharField(_('Internal Transaction Id'), max_length=50, help_text='An Id for the payment generated by us') amount = models.FloatField(default='0.0') type = models.CharField(_('Payment Type'), blank=False, default=PAYMENT_TYPE_DIRECT_DEBIT, choices=TYPE_CHOICES, editable=False, max_length=50) last_action_at = models.DateTimeField(verbose_name=_('Last Action At'), editable=False, auto_now=True) completed_at = models.DateTimeField(verbose_name=_('Completed At'), editable=False, blank=True, null=True) status = models.PositiveSmallIntegerField(_('Payment Status'), blank=False, default=STATUS_NOT_STARTED, choices=STATUS_CHOICES, editable=False) is_reference_payment = models.BooleanField(verbose_name=_('Is reference payment'), default=True, editable=False, help_text='Is this the reference (first) payment in a series or a subscription payment derived from a reference payment?') is_postponed_payment = models.BooleanField(verbose_name=_('Is postponed payment'), default=False, editable=False, help_text='Is this a postponed payment, that gets pre-authorized first, and then cashed-in at some later point?') revoked = models.BooleanField(verbose_name=_('Has been revoked'), default=False) # billing address details, can be saved for some payment methods, but not necessary first_name = models.CharField(blank=True, null=True, max_length=255) last_name = models.CharField(blank=True, null=True, max_length=255) organisation = models.CharField(blank=True, null=True, max_length=255) email = models.EmailField(blank=True, null=True) address = models.CharField(blank=True, null=True, max_length=255) city = models.CharField(blank=True, null=True, max_length=255) postal_code = models.CharField(blank=True, null=True, max_length=255) country = CountryField(blank=True, null=True) backend = models.CharField(_('Backend class used'), max_length=255) extra_data = JSONField(null=True, blank=True) class Meta(object): app_label = 'wechange_payments' verbose_name = _('Payment') verbose_name_plural = _('Payments') def get_type_string(self): return dict(self.TYPE_CHOICES).get(self.type)
class Measurement(models.Model): timestamp = models.DateTimeField(auto_now=False, auto_now_add=True) patient = models.ForeignKey(Patient, on_delete=models.CASCADE) data = JSONField()
class Analysis(models.Model): event = models.ForeignKey(Event, on_delete=models.CASCADE) updated = models.DateTimeField(auto_now=True, auto_now_add=False) start_date = models.DateField(auto_now=True) end_date = models.DateField(auto_now=True) data = JSONField()
class Event(models.Model): timestamp = models.DateTimeField(auto_now=False, auto_now_add=True) case = models.ForeignKey(Case, on_delete=models.CASCADE) doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE) data = JSONField()
class DiarioOficial(CommonMixin): FIELDFILE_NAME = ('arquivo', ) metadata = JSONField(verbose_name=_('Metadados'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) edicao = models.PositiveIntegerField(default=0, verbose_name='Edição') descricao = models.CharField(max_length=250, verbose_name=_('Descrição')) tipo = models.ForeignKey(TipoDeDiario, on_delete=models.PROTECT, verbose_name=_('Tipo do Diário')) data = models.DateField(blank=True, null=True, verbose_name=_('Data')) arquivo = models.FileField(blank=True, null=True, upload_to=diario_upload_path, storage=OverwriteStorage(), verbose_name=_('Arquivo Digital do Diário'), validators=[restringe_tipos_de_arquivo_txt]) normas = models.ManyToManyField( 'norma.NormaJuridica', verbose_name=_('Normas Publicadas no Diário')) data_ultima_atualizacao = models.DateTimeField(blank=True, null=True, auto_now=True, verbose_name=_('Data')) class Meta: verbose_name = _('Diário Oficial') verbose_name_plural = _('Diários Oficiais') ordering = ('-data', 'edicao') def __str__(self): return self.descricao @property def ano(self): return self.data.year def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and self.arquivo: arquivo = self.arquivo self.arquivo = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.arquivo = arquivo return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
class BookingInvoice(RevisionedMixin): PAYMENT_METHOD_CC = 0 PAYMENT_METHOD_BPAY = 1 PAYMENT_METHOD_MONTHLY_INVOICING = 2 PAYMENT_METHOD_OTHER = 3 PAYMENT_METHOD_CHOICES = ( (PAYMENT_METHOD_CC, 'Credit Card'), (PAYMENT_METHOD_BPAY, 'BPAY'), (PAYMENT_METHOD_MONTHLY_INVOICING, 'Monthly Invoicing'), (PAYMENT_METHOD_OTHER, 'Other (Cash/Cheque)'), ) booking = models.ForeignKey(Booking, related_name='invoices') invoice_reference = models.CharField(max_length=50, null=True, blank=True, default='') payment_method = models.SmallIntegerField( choices=PAYMENT_METHOD_CHOICES, default=0 ) # duplicating from ledger Invoice model to allow easier filtering on payment dashboard deferred_payment_date = models.DateField(blank=True, null=True) payment_due_notification_sent = models.BooleanField(default=False) property_cache = JSONField(null=True, blank=True, default={}) def __str__(self): return 'Booking {} : Invoice #{}'.format(self.id, self.invoice_reference) class Meta: app_label = 'commercialoperator' def save(self, *args, **kwargs): self.update_property_cache(False) super(BookingInvoice, self).save(*args, **kwargs) @property def active(self): try: invoice = Invoice.objects.get(reference=self.invoice_reference) return False if invoice.voided else True except Invoice.DoesNotExist: pass return False @property def invoice(self): try: invoice = Invoice.objects.get(reference=self.invoice_reference) return invoice except Invoice.DoesNotExist: pass return False @property def overdue(self): if self.invoice and self.deferred_payment_date and ( self.invoice.payment_status == 'unpaid' or self.invoice.payment_status == 'partially_paid' ) and self.deferred_payment_date < timezone.now().date(): return True return False def get_property_cache(self): ''' Get properties which were previously resolved. ''' if len(self.property_cache) == 0: self.update_property_cache() #if self.processing_status == self.PROCESSING_STATUS_AWAITING_PAYMENT: # self.update_property_cache() return self.property_cache def get_property_cache_key(self, key): ''' Get properties which were previously resolved with key. ''' try: self.property_cache[key] except KeyError: self.update_property_cache() return self.property_cache[key] def update_property_cache(self, save=True): ''' Refresh cached properties with updated properties. ''' logger.debug('BookingInvoice.update_property_cache()') self.property_cache['payment_status'] = self._payment_status if save is True: self.save() return self.property_cache @property def payment_status(self): """ get cached value, if it exists """ if 'payment_status' not in self.property_cache: self.update_property_cache() return self.get_property_cache_key('payment_status') @property def _payment_status(self): if self.invoice: payment_status = self.invoice.payment_status return ' '.join([ i.capitalize() for i in payment_status.replace('_', ' ').split() ]) return 'Unpaid'
class Migration(migrations.Migration): dependencies = [ ('persons', '0005_family'), ('whereabouts', '0004_division'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='Attendee', fields=[ ('id', model_utils.fields.UUIDField(default=uuid4, editable=False, primary_key=True, serialize=False)), ('user', models.OneToOneField(blank=True, default=None, null=True, on_delete=models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), ('created', model_utils.fields.AutoCreatedField( default=django.utils.timezone.now, editable=False, verbose_name='created')), ('modified', model_utils.fields.AutoLastModifiedField( default=django.utils.timezone.now, editable=False, verbose_name='modified')), ('is_removed', models.BooleanField(default=False)), ('division', models.ForeignKey(default=0, blank=False, null=False, on_delete=models.SET(0), to='whereabouts.Division')), ('actual_birthday', models.DateField(blank=True, null=True)), ('estimated_birthday', models.DateField(blank=True, null=True)), ('deathday', models.DateField(blank=True, null=True)), ('first_name', models.CharField(blank=True, db_index=True, max_length=25, null=True)), ('last_name', models.CharField(blank=True, db_index=True, max_length=25, null=True)), ('first_name2', models.CharField(blank=True, db_index=True, max_length=12, null=True)), ('last_name2', models.CharField(blank=True, db_index=True, max_length=8, null=True)), ('gender', models.CharField(choices=GenderEnum.choices(), default=GenderEnum.UNSPECIFIED, max_length=11)), ('photo', PrivateFileField(blank=True, null=True, storage=PrivateFileSystemStorage(), upload_to='attendee_portrait', verbose_name='Photo')), ('progressions', JSONField( blank=True, default=dict, help_text= 'Example: {"Christian": true, "baptized": {"time": "12/31/2020", "place":"SF"}}. Please keep {} here even no data', null=True)), ('infos', JSONField( blank=True, null=True, default=Utility.attendee_infos, help_text= 'Example: {"fixed": {"food_pref": "peanut allergy", "nick_name": "John"}}. Please keep {} here even no data' )), ], options={ 'db_table': 'persons_attendees', 'ordering': ['last_name', 'first_name'], }, bases=(Utility, models.Model), ), # migrations.RunSQL( # sql=""" # ALTER TABLE persons_attendees DROP COLUMN full_name; # ALTER TABLE persons_attendees ADD COLUMN full_name VARCHAR(70) # GENERATED ALWAYS AS (TRIM( # COALESCE(first_name, '') || ' ' || # COALESCE(last_name, '') || ' ' || # COALESCE(last_name2, '') || # COALESCE(first_name2, '') # )) STORED; # CREATE INDEX attendee_full_name_raw # ON persons_attendees (full_name); # """, # # reverse_sql="", # ), # switching to use opencc for language conversion in Attendee.save() migrations.AddIndex( model_name='attendee', index=django.contrib.postgres.indexes.GinIndex( fields=['infos'], name='attendee_infos_gin'), ), migrations.AddIndex( model_name='attendee', index=django.contrib.postgres.indexes.GinIndex( fields=['progressions'], name='attendee_progressions_gin'), ), ]
class DocumentoAcessorioAdministrativo(CountPageMixin): FIELDFILE_NAME = ('arquivo', ) metadata = JSONField(verbose_name=_('Metadados'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) documento = models.ForeignKey(DocumentoAdministrativo, on_delete=models.PROTECT) tipo = models.ForeignKey(TipoDocumentoAdministrativo, on_delete=models.PROTECT, verbose_name=_('Tipo')) nome = models.CharField(max_length=30, verbose_name=_('Nome')) arquivo = PortalFileField(blank=True, null=True, upload_to=texto_upload_path, storage=OverwriteStorage(), verbose_name=_('Arquivo'), max_length=512) data = models.DateField(blank=True, null=True, verbose_name=_('Data')) autor = models.CharField(max_length=50, blank=True, verbose_name=_('Autor')) assunto = models.TextField(blank=True, verbose_name=_('Assunto')) indexacao = models.TextField(blank=True) class Meta: verbose_name = _('Documento Acessório') verbose_name_plural = _('Documentos Acessórios') def __str__(self): return self.nome def delete(self, using=None, keep_parents=False): if self.arquivo: self.arquivo.delete() return models.Model.delete(self, using=using, keep_parents=keep_parents) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and self.arquivo: arquivo = self.arquivo self.arquivo = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.arquivo = arquivo return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
class StatementLine(models.Model): """ Records an single imported bank statement line A StatementLine is purely a utility to aid in the creation of transactions (in the process known as reconciliation). StatementLines have no impact on account balances. However, the :meth:`StatementLine.create_transaction()` method can be used to create a transaction based on the information in the StatementLine. Attributes: uuid (SmallUUID): UUID for statement line. Use to prevent leaking of IDs (if desired). timestamp (datetime): The datetime when the object was created. date (date): The date given by the statement line statement_import (StatementImport): The import to which the line belongs amount (Decimal): The amount for the statement line, positive or nagative. description (str): Any description/memo information provided transaction (Transaction): Optionally, the transaction created for this statement line. This normally occurs during reconciliation. See also :meth:`StatementLine.create_transaction()`. """ uuid = SmallUUIDField(default=uuid_default(), editable=False) timestamp = models.DateTimeField(default=timezone.now) date = models.DateField() statement_import = models.ForeignKey(StatementImport, related_name='lines', on_delete=models.CASCADE) amount = models.DecimalField(max_digits=13, decimal_places=2) description = models.TextField(default='', blank=True) type = models.CharField(max_length=50, default='') # TODO: Add constraint to ensure transaction amount = statement line amount # TODO: Add constraint to ensure one statement line per transaction transaction = models.ForeignKey( Transaction, default=None, blank=True, null=True, help_text='Reconcile this statement line to this transaction', on_delete=models.SET_NULL) source_data = JSONField( default={}, help_text='Original data received from the data source.') objects = StatementLineManager() def natural_key(self): return (self.uuid, ) @property def is_reconciled(self): """Has this statement line been reconciled? Determined as ``True`` if :attr:`transaction` has been set. Returns: bool: ``True`` if reconciled, ``False`` if not. """ return bool(self.transaction) @db_transaction.atomic() def create_transaction(self, to_account): """Create a transaction for this statement amount and account, into to_account This will also set this StatementLine's ``transaction`` attribute to the newly created transaction. Args: to_account (Account): The account the transaction is into / out of. Returns: Transaction: The newly created (and committed) transaction. """ from_account = self.statement_import.bank_account transaction = Transaction.objects.create() Leg.objects.create(transaction=transaction, account=from_account, amount=+(self.amount * -1)) Leg.objects.create(transaction=transaction, account=to_account, amount=-(self.amount * -1)) transaction.date = self.date transaction.save() self.transaction = transaction self.save() return transaction
return None return super(DateTimeAwareJSONField, self).to_python(decode_datetime_objects(value)) def get_prep_lookup(self, lookup_type, value): if lookup_type in ('has_key', 'has_keys', 'has_any_keys'): return value if isinstance(value, (dict, list)): return Json(value, dumps=partial(json.dumps, cls=DateTimeAwareJSONEncoder)) return super(JSONField, self).get_prep_lookup(lookup_type, value) def validate(self, value, model_instance): super(JSONField, self).validate(value, model_instance) try: json.dumps(value, cls=DateTimeAwareJSONEncoder) except TypeError: raise ValidationError( self.error_messages['invalid'], code='invalid', params={'value': value}, ) JSONField.register_lookup(lookups.DataContains) JSONField.register_lookup(lookups.ContainedBy) JSONField.register_lookup(lookups.HasKey) JSONField.register_lookup(lookups.HasKeys) JSONField.register_lookup(lookups.HasAnyKeys)
class View(NameAndDescription, CreateModifyFields): """ Class to represent different views of the table attached to a workflow. It only contains (aside from the basic fields) a formula to filter rows, and a subset of the columns in the workflow. @DynamicAttrs """ workflow = models.ForeignKey( Workflow, db_index=True, null=False, blank=False, on_delete=models.CASCADE, related_name='views') # Set of columns for the personalised action IN (subset of the matrix # columns columns = models.ManyToManyField( Column, verbose_name=_("Subset of columns to show"), related_name='views' ) # Formula to select a subset of rows for action IN formula = JSONField( verbose_name=_("Subset of rows to show"), default=dict, blank=True, null=True, help_text=_('Preselect rows satisfying this condition')) # Number of rows allowed by the formula. nrows = None def __str__(self): return self.name @property def num_columns(self): """Number of columns considered by this view. :return: Number of elements in the columns relation """ return self.columns.count() @property def num_rows(self): """Number of rows considered by this view. :return: Number of rows resulting from using the formula """ if not self.nrows: self.nrows = sql.get_num_rows( self.workflow.get_data_frame_table_name(), self.formula ) return self.nrows @property def column_names(self): """List of column names. :return: List of column names """ return [column.name for column in self.columns.all()] def log(self, user, operation_type: str, **kwargs): """Log the operation with the object.""" payload = { 'id': self.id, 'name': self.name, 'columns': [col.name for col in self.columns.all()], 'formula': formula.evaluate( self.formula, formula.EVAL_TXT), 'nrows': self.nrows} payload.update(kwargs) return Log.objects.register( user, operation_type, self.workflow, payload) class Meta: """Define uniqueness with name in workflow and order by name.""" unique_together = ('name', 'workflow') ordering = ['name', ]
class NormaJuridica(CountPageMixin): FIELDFILE_NAME = ('texto_integral', ) ESFERA_FEDERACAO_CHOICES = Choices( ('M', 'municipal', _('Municipal')), ('E', 'estadual', _('Estadual')), ('F', 'federal', _('Federal')), ) metadata = JSONField(verbose_name=_('Metadados'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) texto_integral = PortalFileField( blank=True, null=True, upload_to=norma_upload_path, verbose_name=_('Texto Integral'), storage=OverwriteStorage(), validators=[restringe_tipos_de_arquivo_txt], max_length=512) tipo = models.ForeignKey(TipoNormaJuridica, on_delete=models.PROTECT, verbose_name=_('Tipo da Norma Jurídica')) materia = models.ForeignKey(MateriaLegislativa, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('Matéria')) numero = models.CharField(max_length=8, verbose_name=_('Número')) ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'), choices=RANGE_ANOS) esfera_federacao = models.CharField(max_length=1, verbose_name=_('Esfera Federação'), choices=ESFERA_FEDERACAO_CHOICES) data = models.DateField(blank=False, null=True, verbose_name=_('Data')) data_publicacao = models.DateField(blank=True, null=True, verbose_name=_('Data de Publicação')) veiculo_publicacao = models.CharField( max_length=30, blank=True, verbose_name=_('Veículo de Publicação')) pagina_inicio_publicacao = models.PositiveIntegerField( blank=True, null=True, verbose_name=_('Pg. Início')) pagina_fim_publicacao = models.PositiveIntegerField( blank=True, null=True, verbose_name=_('Pg. Fim')) ementa = models.TextField(verbose_name=_('Ementa')) indexacao = models.TextField(blank=True, verbose_name=_('Indexação')) observacao = models.TextField(blank=True, verbose_name=_('Observação')) complemento = models.NullBooleanField(blank=True, verbose_name=_('Complementar ?'), choices=YES_NO_CHOICES) # XXX was a CharField (attention on migrate) assuntos = models.ManyToManyField(AssuntoNorma, blank=True, verbose_name=_('Assuntos')) data_vigencia = models.DateField(blank=True, null=True, verbose_name=_('Data Fim Vigência')) timestamp = models.DateTimeField(null=True) texto_articulado = GenericRelation(TextoArticulado, related_query_name='texto_articulado') data_ultima_atualizacao = models.DateTimeField(blank=True, null=True, auto_now=True, verbose_name=_('Data')) autores = models.ManyToManyField(Autor, through='AutoriaNorma', through_fields=('norma', 'autor'), symmetrical=False) norma_de_destaque = models.BooleanField( verbose_name=_('Norma de Destaque ?'), choices=YES_NO_CHOICES, default=False) apelido = models.TextField(blank=True, verbose_name=_('Apelido')) user = models.ForeignKey(get_settings_auth_user_model(), verbose_name=_('Usuário'), on_delete=models.PROTECT, null=True, blank=True) ip = models.CharField(verbose_name=_('IP'), max_length=30, blank=True, default='') _certidao = GenericRelation(CertidaoPublicacao, related_query_name='normajuridica_cert') def diariooficial(self): if self.veiculo_publicacao: if not self.diariooficial_set.filter( edicao=self.veiculo_publicacao).exists(): from cmj.diarios.models import DiarioOficial d = DiarioOficial.objects.filter( edicao=self.veiculo_publicacao).last() if d: d.normas.add(self) return self.diariooficial_set.all() class Meta: verbose_name = _('Norma Jurídica') verbose_name_plural = _('Normas Jurídicas') ordering = ['-data', '-numero'] def get_normas_relacionadas(self): principais = NormaRelacionada.objects.filter( norma_principal=self.id).order_by('norma_principal__data', 'norma_relacionada__data') relacionadas = NormaRelacionada.objects.filter( norma_relacionada=self.id).order_by('norma_principal__data', 'norma_relacionada__data') return (principais, relacionadas) def get_anexos_norma_juridica(self): anexos = AnexoNormaJuridica.objects.filter(norma=self.id) return anexos @property def is_signed(self): try: return self.metadata and self.metadata['signs'] and \ self.metadata['signs']['texto_integral'] and \ self.metadata['signs']['texto_integral']['signs'] except: return False @property def certidao(self): return self._certidao.all().first() @property def __descr__(self): return self.ementa def __str__(self): return _('%(tipo)s nº %(numero)s de %(data)s') % { 'tipo': self.tipo, 'numero': self.numero, 'data': defaultfilters.date(self.data, "d \d\e F \d\e Y") } @property def epigrafe(self): return _('%(tipo)s nº %(numero)s de %(data)s') % { 'tipo': self.tipo, 'numero': self.numero, 'data': defaultfilters.date(self.data, "d \d\e F \d\e Y") } def delete(self, using=None, keep_parents=False): if self.texto_integral: self.texto_integral.delete() return models.Model.delete(self, using=using, keep_parents=keep_parents) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and self.texto_integral: texto_integral = self.texto_integral self.texto_integral = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.texto_integral = texto_integral return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
class BaseTaggableObjectModel(LastVisitedMixin, IndexingUtilsMixin, AttachableObjectModel): """ Represents the base for all cosinnus main models. Each inheriting model has a set of simple ``tags`` which are just strings. Additionally each model has a ``media_tag`` that refers to all general tags like a location, people and so on. """ media_tag = models.OneToOneField(settings.COSINNUS_TAG_OBJECT_MODEL, blank=True, null=True, on_delete=models.SET_NULL) group = models.ForeignKey(settings.COSINNUS_GROUP_OBJECT_MODEL, verbose_name=_('Team'), related_name='%(app_label)s_%(class)s_set', on_delete=models.CASCADE) title = models.CharField(_('Title'), max_length=255) slug = models.SlugField(max_length=55, blank=True) # human readable part is 50 chars creator = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('Creator'), on_delete=models.CASCADE, null=True, related_name='%(app_label)s_%(class)s_set') created = models.DateTimeField( verbose_name=_('Created'), editable=False, auto_now_add=True) last_modified = models.DateTimeField( verbose_name=_('Last modified'), editable=False, auto_now=True) last_action = models.DateTimeField( verbose_name='Last action date', auto_now_add=True, help_text='A datetime for when a significant action last happened for this object, '\ 'which users might be interested in. I.e. new comments, special edits, etc.') last_action_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='Last action user', on_delete=models.SET_NULL, null=True, related_name='+', help_text='The user which caused the last significant action to update the `last_action` datetime.') settings = JSONField(default=dict, blank=True, null=True) class Meta(object): abstract = True unique_together = (('group', 'slug'),) def __str__(self): return self.title def __repr__(self): return "<tagged object {0} {1} (ID: {2})>".format( self.__class__.__name__, self.title, self.pk) def save(self, *args, **kwargs): created = bool(self.pk) == False unique_aware_slugify(self, 'title', 'slug', group=self.group) self.title = clean_single_line_text(self.title) if hasattr(self, '_media_tag_cache'): del self._media_tag_cache # set last action timestamp if not self.last_action: self.last_action = self.created if not self.last_action_user: self.last_action_user = self.creator super(BaseTaggableObjectModel, self).save(*args, **kwargs) if not getattr(self, 'media_tag', None): self.media_tag = get_tag_object_model().objects.create() self.save() if created: pass def on_save_added_tagged_persons(self, set_users): """ Called by the taggable form whenever this object is saved and -new- persons have been added as tagged! This can be overridden in specific TaggableObjects for a more specific notification email message. Just add extra={'mail_template':'<your_template>', 'subject_template':'<your_template>'} """ # exclude creator from audience always set_users -= set([self.creator]) cosinnus_notifications.user_tagged_in_object.send(sender=self, user=self.creator, obj=self, audience=list(set_users)) def media_tag_object(self): key = '_media_tag_cache' if not hasattr(self, key): if self.media_tag_id is None: setattr(self, key, self.group.media_tag) else: setattr(self, key, self.media_tag) return getattr(self, key) @property def sort_key(self): """ The main property on which this object model is sorted """ if not self.created: return now() return self.created def grant_extra_read_permissions(self, user): """ An overridable check for whether this object grants certain users read permissions even though by general rules that user couldn't read the object. @param user: The user to check for extra permissions for """ return False def grant_extra_write_permissions(self, user, **kwargs): """ An overridable check for whether this object grants certain users write permissions even though by general rules that user couldn't write the object. @param user: The user to check for extra permissions for """ return False def get_tagged_persons_hash(self): """ Returns a hashable tuple of sorted list of ids of all tagged persons. Usuable to compare equality of attached files to objects. """ return tuple(sorted(list(self.media_tag.persons.all().values_list('id', flat=True)))) def get_delete_url(self): """ Similar to get_absolute_url, this returns the URL for this object's implemented delete view. Needs to be set by a specific implementation of BaseTaggableObjectModel """ raise ImproperlyConfigured("The get_delete_url function must be implemented for model '%s'" % self.__class__) def get_cosinnus_app_name(self): return app_registry.get_name(self.__class__.__module__.split('.')[0]) def render_additional_notification_content_rows(self): """ Used when rendering email notifications for an object. Any list of html strings returned here will be added, on row each, after the object_text of the notification object. Note: use mark_safe on the html strings if you do not wish them to be escaped! @return: An array of HTML strings or [] """ content_snippets = [] tag_object = self.media_tag if tag_object.location and tag_object.location_url: from cosinnus.models.group import CosinnusPortal location_html = render_to_string('cosinnus/html_mail/content_snippets/tagged_location.html', {'tag_object': tag_object, 'domain': CosinnusPortal.get_current().get_domain()}) content_snippets.append(mark_safe(location_html)) return content_snippets def update_last_action(self, last_action_dt, last_action_user=None, save=True): """ Sets the `last_action` timestamp which is used for sorting items for timely relevance to show the users in their timelines or similar. """ self.last_action = last_action_dt if last_action_user: self.last_action_user = last_action_user if save: self.save()
class Payment(models.Model): """ Model which store information about user payments Can link to tx_hash or Charge object, depending on what type of payment user choose """ TRANSFER_STATES_DEFAULT = ('WAITING_FOR_TRANSFER', 'DONE', 'ERROR', 'RETURNED', 'QUEUED', 'PENDING') ADAPTED_TRANSFER_STATES_DEFAULT = ('WAITING_FOR_VALIDATION', 'SUCCESS', 'FAIL', 'RETURN', 'WAITING_FOR_RELAY', 'PENDING') COLLECTION_STATES_DEFAULT = ('NOT_COLLECTED', 'COLLECTED', 'ERROR') TRANSFER_STATES = list( zip(TRANSFER_STATES_DEFAULT, TRANSFER_STATES_DEFAULT)) COLLECTION_STATES = list( zip(COLLECTION_STATES_DEFAULT, COLLECTION_STATES_DEFAULT)) exchange_request = models.ForeignKey(ExchangeRequest, on_delete=models.CASCADE, null=True) charge = models.ForeignKey('quantum.Charge', on_delete=models.CASCADE, null=True) tx_hash = models.CharField(max_length=100, null=True, default='') currency = models.CharField(max_length=50, null=True, default='') from_address = models.CharField(max_length=100, null=True, default='') original_amount = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=0) rate = models.DecimalField(max_digits=512, decimal_places=0) sent_amount = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=0) created_date = models.DateTimeField(auto_now_add=True) transfer_state = FSMField(default=TRANSFER_STATES_DEFAULT[0], choices=TRANSFER_STATES) collection_state = FSMField(default=COLLECTION_STATES_DEFAULT[0], choices=COLLECTION_STATES) collection_tx_hash = models.CharField(max_length=100, null=True, default='') returned_tx_hash = models.CharField(max_length=100, null=True, default='') transfer_state_history = JSONField( default=generate_transfer_state_history_default) @property def adapted_state(self): """ Temporary addon to adjust statuses for /payments/get-status/ endpoint """ return dict( zip(self.TRANSFER_STATES_DEFAULT, self.ADAPTED_TRANSFER_STATES_DEFAULT))[self.transfer_state] # States change @transition(field=transfer_state, source=['WAITING_FOR_TRANSFER', 'ERROR', 'PENDING'], target='DONE') def state_transfer_done(self): pass @transition(field=transfer_state, source='*', target='ERROR') def state_transfer_error(self): print('Transfer not completed, reverting payment', flush=True) @transition(field=transfer_state, source='*', target='RETURNED') def state_transfer_returned(self): pass @transition(field=transfer_state, source='*', target='QUEUED') def state_transfer_queued(self): pass @transition(field=transfer_state, source=['QUEUED', 'WAITING_FOR_TRANSFER'], target='PENDING') def state_transfer_pending(self): pass @transition(field=collection_state, source=['NOT_COLLECTED', 'ERROR'], target='COLLECTED') def state_collect_duc(self): pass @transition(field=collection_state, source='*', target='ERROR') def state_error_collect_duc(self): pass
class Return(models.Model): """ A number of requirements relating to a Licence condition recorded during the Licence period. """ RETURN_PROCESSING_STATUS_DUE = 'due' RETURN_PROCESSING_STATUS_OVERDUE = 'overdue' RETURN_PROCESSING_STATUS_DRAFT = 'draft' RETURN_PROCESSING_STATUS_FUTURE = 'future' RETURN_PROCESSING_STATUS_WITH_CURATOR = 'with_curator' RETURN_PROCESSING_STATUS_ACCEPTED = 'accepted' RETURN_PROCESSING_STATUS_PAYMENT = 'payment' PROCESSING_STATUS_CHOICES = ((RETURN_PROCESSING_STATUS_DUE, 'Due'), (RETURN_PROCESSING_STATUS_OVERDUE, 'Overdue'), (RETURN_PROCESSING_STATUS_DRAFT, 'Draft'), (RETURN_PROCESSING_STATUS_FUTURE, 'Future'), (RETURN_PROCESSING_STATUS_WITH_CURATOR, 'With Curator'), (RETURN_PROCESSING_STATUS_ACCEPTED, 'Accepted'), (RETURN_PROCESSING_STATUS_PAYMENT, 'Awaiting Payment')) RETURN_CUSTOMER_STATUS_DUE = 'due' RETURN_CUSTOMER_STATUS_OVERDUE = 'overdue' RETURN_CUSTOMER_STATUS_DRAFT = 'draft' RETURN_CUSTOMER_STATUS_FUTURE = 'future' RETURN_CUSTOMER_STATUS_UNDER_REVIEW = 'under_review' RETURN_CUSTOMER_STATUS_ACCEPTED = 'accepted' RETURN_CUSTOMER_STATUS_DISCARD = 'discard' RETURN_CUSTOMER_STATUS_PAYMENT = 'payment' # Displayable choices for customer status. CUSTOMER_DISPLAYABLE_STATE = { RETURN_CUSTOMER_STATUS_DUE: 'Due', RETURN_CUSTOMER_STATUS_OVERDUE: 'Overdue', RETURN_CUSTOMER_STATUS_DRAFT: 'Draft', RETURN_CUSTOMER_STATUS_FUTURE: 'Future Submission', RETURN_CUSTOMER_STATUS_UNDER_REVIEW: 'Under Review', RETURN_CUSTOMER_STATUS_ACCEPTED: 'Accepted', RETURN_CUSTOMER_STATUS_DISCARD: 'Discarded', RETURN_CUSTOMER_STATUS_PAYMENT: 'Awaiting Payment', } # status that allow a customer to edit a Return. CUSTOMER_EDITABLE_STATE = [ RETURN_PROCESSING_STATUS_DRAFT, RETURN_PROCESSING_STATUS_PAYMENT, ] # List of statuses from above that allow a customer to view a Return. # (read-only) CUSTOMER_VIEWABLE_STATE = [ RETURN_PROCESSING_STATUS_WITH_CURATOR, RETURN_PROCESSING_STATUS_ACCEPTED ] lodgement_number = models.CharField(max_length=9, blank=True, default='') application = models.ForeignKey(Application, related_name='returns_application') licence = models.ForeignKey('wildlifecompliance.WildlifeLicence', related_name='returns_licence') due_date = models.DateField() processing_status = models.CharField( choices=PROCESSING_STATUS_CHOICES, max_length=20, default=RETURN_PROCESSING_STATUS_FUTURE) assigned_to = models.ForeignKey(EmailUser, related_name='returns_curator', null=True, blank=True) condition = models.ForeignKey(ApplicationCondition, blank=True, null=True, related_name='returns_condition', on_delete=models.SET_NULL) lodgement_date = models.DateTimeField(blank=True, null=True) submitter = models.ForeignKey(EmailUser, blank=True, null=True, related_name='returns_submitter') reminder_sent = models.BooleanField(default=False) post_reminder_sent = models.BooleanField(default=False) return_type = models.ForeignKey(ReturnType, null=True) nil_return = models.BooleanField(default=False) comments = models.TextField(blank=True, null=True) return_fee = models.DecimalField(max_digits=8, decimal_places=2, default='0') property_cache = JSONField(null=True, blank=True, default={}) class Meta: app_label = 'wildlifecompliance' def __str__(self): return self.lodgement_number def save(self, *args, **kwargs): self.update_property_cache(False) super(Return, self).save(*args, **kwargs) ''' Append 'R' to Return id to generate Return lodgement number. ''' if self.lodgement_number == '': new_lodgement_id = 'R{0:06d}'.format(self.pk) self.lodgement_number = new_lodgement_id self.save() def get_property_cache(self): ''' Get properties which were previously resolved. ''' if len(self.property_cache) == 0: self.update_property_cache() return self.property_cache def update_property_cache(self, save=True): ''' Refresh cached properties with updated properties. ''' # self.property_cache['payment_status'] = self.payment_status if save is True: self.save() return self.property_cache def get_property_cache_key(self, key): ''' Get properties which were previously resolved with key. ''' try: self.property_cache[key] except KeyError: self.update_property_cache() return self.property_cache @property def activity(self): return self.application.activity @property def title(self): return self.application.title @property def holder(self): return self.application.applicant @property def resources(self): return self.return_type.data_descriptor.get('resources', []) @property def format(self): return self.return_type.data_format @property def template(self): """ Return data spreadsheet template for uploading information. :return: spreadsheet template format. """ template = self.return_type.data_template return self.return_type.data_template.url if template else None @property def has_question(self): """ Property defining if the Return is Question based. :return: Boolean """ return True if self.format == ReturnType.FORMAT_QUESTION else False @property def has_data(self): """ Property defining if the Return is Data based. :return: Boolean """ return True if self.format == ReturnType.FORMAT_DATA else False @property def has_sheet(self): """ Property defining if the Return is Running Sheet based. :return: Boolean """ return True if self.format == ReturnType.FORMAT_SHEET else False @property def has_species_list(self): """ Boolean Property defining if the Return has an associated species list. """ return False if self.return_type.with_no_species else True @property def customer_status(self): """ Property defining external status in relation to processing status. :return: External Status. """ DUE = self.RETURN_CUSTOMER_STATUS_DUE OVERDUE = self.RETURN_CUSTOMER_STATUS_OVERDUE DRAFT = self.RETURN_CUSTOMER_STATUS_DRAFT FUTURE = self.RETURN_CUSTOMER_STATUS_FUTURE UNDER_REVIEW = self.RETURN_CUSTOMER_STATUS_UNDER_REVIEW ACCEPTED = self.RETURN_CUSTOMER_STATUS_ACCEPTED workflow_mapper = { self.RETURN_PROCESSING_STATUS_DUE: DUE, self.RETURN_PROCESSING_STATUS_OVERDUE: OVERDUE, self.RETURN_PROCESSING_STATUS_DRAFT: DRAFT, self.RETURN_PROCESSING_STATUS_FUTURE: DRAFT, self.RETURN_PROCESSING_STATUS_WITH_CURATOR: UNDER_REVIEW, self.RETURN_PROCESSING_STATUS_ACCEPTED: ACCEPTED, self.RETURN_PROCESSING_STATUS_PAYMENT: UNDER_REVIEW } return workflow_mapper.get(self.processing_status, FUTURE) @property def payment_status(self): """ Property defining fee status for this Return. :return: """ if not self.return_type.fee_required: return ReturnInvoice.PAYMENT_STATUS_NOT_REQUIRED else: if self.invoices.count() == 0: return ReturnInvoice.PAYMENT_STATUS_UNPAID else: try: latest_invoice = Invoice.objects.get( reference=self.invoices.latest('id').invoice_reference) except ReturnInvoice.DoesNotExist: return ReturnInvoice.PAYMENT_STATUS_UNPAID return latest_invoice.payment_status @property def return_fee_paid(self): return self.payment_status in [ ReturnInvoice.PAYMENT_STATUS_NOT_REQUIRED, ReturnInvoice.PAYMENT_STATUS_PAID, ReturnInvoice.PAYMENT_STATUS_OVERPAID, ] @property def has_payment(self): """ Property defining if payment is required for this Return. :return: """ has_payment = False if self.get_latest_invoice(): has_payment = True return has_payment @property def total_paid_amount(self): ''' Property defining the total fees already paid for this licence Return. ''' amount = 0 if self.invoices.count() > 0: for invoice in self.invoices.all(): detail = Invoice.objects.get( reference=invoice.invoice_reference) amount += detail.payment_amount amount -= detail.refund_amount return amount @property def activity_curators(self): ''' QuerySet of authorised licence activity curators for this return. ''' groups = self.get_permission_groups('return_curator').values_list( 'id', flat=True) return EmailUser.objects.filter(groups__id__in=groups).distinct() @transaction.atomic def set_submitted(self, request): ''' TODO:AYN This is redundant. ReturnService. ''' try: submit_status = [ Return.RETURN_PROCESSING_STATUS_FUTURE, Return.RETURN_PROCESSING_STATUS_DUE, Return.RETURN_PROCESSING_STATUS_OVERDUE, Return.RETURN_PROCESSING_STATUS_DRAFT, ] CURATOR = Return.RETURN_PROCESSING_STATUS_WITH_CURATOR if self.processing_status in submit_status: self.processing_status = CURATOR self.submitter = request.user self.save() # code for amendment returns is still to be added, so # lodgement_date is set outside if statement self.lodgement_date = timezone.now() self.save() # this below code needs to be reviewed # self.save(version_comment='Return submitted:{}'.format(self.id)) # self.application.save( # version_comment='Return submitted:{}'.format(self.id)) self.log_user_action( ReturnUserAction.ACTION_SUBMIT_REQUEST.format(self), request) send_external_submit_email_notification(request, self) # send_submit_email_notification(request,self) except BaseException: raise def set_return_species(self): ''' Set the species available for this return. ''' try: return_table = [] specie_names = [] if self.return_type.with_application_species: specie_names = self.get_application_return_species() elif self.return_type.with_regulated_species: specie_names = self.get_regulated_return_species() for name in specie_names: return_table.append(ReturnTable(name=name, ret_id=str(self.id))) if return_table: ReturnTable.objects.bulk_create(return_table) except BaseException as e: logger.error('set_return_speces() ID: {0} - {1}'.format( self.id, e)) def get_application_return_species(self): ''' Set the species available for this return. ''' species = [] return species def get_regulated_return_species(self): ''' Set the species available for this return. ''' species = [] return species def set_future_return_species(self): ''' Set the species available for this return. ''' SPECIES = ApplicationFormDataRecord.COMPONENT_TYPE_SELECT_SPECIES STATUS = [ # Return.RETURN_PROCESSING_STATUS_FUTURE 'NO SPECIES ALLOWED' ] if self.processing_status not in STATUS: ''' Species are only set for FUTURE processing status. ''' return species_qs = ApplicationFormDataRecord.objects.values( 'value', ).filter( licence_activity_id=self.condition.licence_activity_id, licence_purpose_id=self.condition.licence_purpose_id, application_id=self.condition.application_id, component_type=SPECIES, ) return_table = [] specie_ids = [] for specie_id in species_qs: specie_ids += specie_id['value'] for id_name in specie_ids: return_table.append(ReturnTable(name=id_name, ret_id=str(self.id))) if return_table: ReturnTable.objects.bulk_create(return_table) def set_processing_status(self, status): ''' Set the processing status for this Return. ''' self.processing_status = status self.save() def set_return_fee(self, fee): ''' Set the submission fee for this return. ''' self.return_fee = fee self.save() def get_customer_status(self): ''' Get displayable customer status. ''' return self.CUSTOMER_DISPLAYABLE_STATE.get(self.customer_status) def get_latest_invoice(self): ''' Get latest invoice for this Return. ''' latest_invoice = None if self.invoices.count() > 0: try: latest_invoice = Invoice.objects.get( reference=self.invoices.latest('id').invoice_reference) except Invoice.DoesNotExist: return None return latest_invoice def get_permission_groups(self, codename): ''' Get the queryset of ActivityPermissionGroups matching this return based on licence activity. ''' from wildlifecompliance.components.applications.models import ( ActivityPermissionGroup) selected_activity_ids = ApplicationCondition.objects.filter( id=self.condition_id, licence_activity__isnull=False).values_list('licence_activity__id', flat=True) if not selected_activity_ids: return ActivityPermissionGroup.objects.none() return ActivityPermissionGroup.get_groups_for_activities( selected_activity_ids, codename) @transaction.atomic def accept(self, request): ''' TODO:AYN This is redundant. ReturnService. ''' try: self.processing_status = Return.RETURN_PROCESSING_STATUS_ACCEPTED self.save() self.log_user_action( ReturnUserAction.ACTION_ACCEPT_REQUEST.format(self), request) send_return_accept_email_notification(self, request) except BaseException: raise def save_return_table(self, table_name, table_rows, request): """ Persist Return Table of data to database. :param table_name: :param table_rows: :param request: :return: """ try: # wrap atomic context here to allow natural handling of a Record # Modified concurrent error. (optimistic lock) # get the Return Table record and save immediately to check if # it has been concurrently modified. return_table, created = ReturnTable.objects.get_or_create( name=table_name, ret=self) return_table.save() # delete any existing rows as they will all be recreated return_table.returnrow_set.all().delete() return_rows = [ ReturnRow(return_table=return_table, data=row) for row in table_rows ] ReturnRow.objects.bulk_create(return_rows) # log transaction self.log_user_action( ReturnUserAction.ACTION_SAVE_REQUEST.format(self), request) except RecordModifiedError: raise IntegrityError( 'A concurrent save occurred please refresh page details.') except BaseException: raise def log_user_action(self, action, request): return ReturnUserAction.log_action(self, action, request.user)
class DocumentoAdministrativo(models.Model): tipo = models.ForeignKey(TipoDocumentoAdministrativo, on_delete=models.PROTECT, verbose_name=_('Tipo Documento')) numero = models.PositiveIntegerField(verbose_name=_('Número')) ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'), choices=RANGE_ANOS) protocolo = models.ForeignKey(Protocolo, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('Protocolo')) materia = models.ForeignKey(MateriaLegislativa, blank=True, null=True, on_delete=models.PROTECT, related_name='documentoadministrativo_set', verbose_name=_('Matéria Vinculada')) data = models.DateField(verbose_name=_('Data')) temp_migracao_doc_acessorio = models.PositiveIntegerField( blank=True, null=True, verbose_name=_( 'Field temporário para migração dos docs acessórios da procuradoria' )) temp_migracao_sislegis = models.PositiveIntegerField( blank=True, null=True, verbose_name=_( 'Field temporário para migração dos docs adms do sislegis')) old_path = models.TextField( verbose_name=_('Path antigo para Sislegis - Publicações'), blank=True, null=True, default=None) old_json = JSONField(verbose_name=_('Json from origin import'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) epigrafe = models.CharField(max_length=1000, blank=True, verbose_name=_('Epigrafe / Título')) interessado = models.CharField(max_length=1000, blank=True, verbose_name=_('Interessado')) autor = models.ForeignKey(Autor, blank=True, null=True, on_delete=models.PROTECT) dias_prazo = models.PositiveIntegerField(blank=True, null=True, verbose_name=_('Dias Prazo')) data_fim_prazo = models.DateField(blank=True, null=True, verbose_name=_('Data Fim Prazo')) data_vencimento = models.DateField(blank=True, null=True, verbose_name=_('Data de Vencimento')) tramitacao = models.BooleanField(verbose_name=_('Em Tramitação?'), choices=YES_NO_CHOICES, default=False) assunto = models.TextField(blank=True, null=True, verbose_name=_('Assunto')) numero_externo = models.PositiveIntegerField( blank=True, null=True, verbose_name=_('Número Externo')) observacao = models.TextField(blank=True, verbose_name=_('Resumo')) texto_integral = PortalFileField(blank=True, null=True, storage=OverwriteStorage(), upload_to=texto_upload_path, verbose_name=_('Texto Integral')) anexados = models.ManyToManyField('self', blank=True, through='Anexado', symmetrical=False, related_name='anexo_de', through_fields=('documento_principal', 'documento_anexado')) workspace = models.ForeignKey(AreaTrabalho, verbose_name=_('Área de Trabalho'), related_name='documentoadministrativo_set', blank=True, null=True, on_delete=PROTECT) data_ultima_atualizacao = models.DateTimeField(blank=True, null=True, auto_now=True, verbose_name=_('Data')) _certidao = GenericRelation( CertidaoPublicacao, related_query_name='documentoadministrativo_cert') class Meta: verbose_name = _('Documento Administrativo') verbose_name_plural = _('Documentos Administrativos') @property def certidao(self): return self._certidao.all().first() @property def __descr__(self): return self.assunto def __str__(self): if self.epigrafe: return '%s' % self.epigrafe return _( '%(sigla)s - %(tipo)s nº %(numero)s/%(ano)s %(interessado)s') % { 'sigla': self.tipo.sigla, 'tipo': self.tipo, 'numero': self.numero, 'ano': self.ano, 'interessado': ('(%s)' % self.interessado) if self.interessado else '' } def delete(self, using=None, keep_parents=False): texto_integral = self.texto_integral result = models.Model.delete(self, using=using, keep_parents=keep_parents) if texto_integral: texto_integral.delete(save=False) return result def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and self.texto_integral: texto_integral = self.texto_integral self.texto_integral = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.texto_integral = texto_integral return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
class ReturnType(models.Model): """ A definition to identify the format used to facilitate Return. """ FORMAT_SHEET = 'sheet' FORMAT_QUESTION = 'question' FORMAT_DATA = 'data' FORMAT_CHOICES = ((FORMAT_SHEET, 'Sheet'), (FORMAT_QUESTION, 'Question'), (FORMAT_DATA, 'Data')) # Species list for this Return Type can be regulated, application-based or # none. SPECIES_LIST_REGULATED = 'regulated' SPECIES_LIST_APPLICATION = 'application' SPECIES_LIST_NONE = 'none' SPECIES_LIST_CHOICES = ((SPECIES_LIST_REGULATED, 'Regulated Species List'), (SPECIES_LIST_APPLICATION, 'Application Species List'), (SPECIES_LIST_NONE, 'No Species List')) name = models.CharField(null=True, blank=True, max_length=100) description = models.TextField(null=True, blank=True, max_length=256) data_descriptor = JSONField() data_format = models.CharField('Data format', max_length=30, choices=FORMAT_CHOICES, default=FORMAT_DATA) # data_template is only used by ReturnData Format for upload. data_template = models.FileField(upload_to=template_directory_path, null=True, blank=True) fee_required = models.BooleanField(default=False) # fee_amount is a base amount required for the Return Type. fee_amount = models.DecimalField(max_digits=8, decimal_places=2, default='0') # fee_name is an optional field for fee and can be used to correspond to # JSON property. fee_name = models.CharField(null=True, blank=True, max_length=50) oracle_account_code = models.CharField(max_length=100, default='') replaced_by = models.ForeignKey('self', on_delete=models.PROTECT, blank=True, null=True) version = models.SmallIntegerField(default=1, blank=False, null=False) # species_list is the type of species associated with this Return Type. species_list = models.CharField('Species List', max_length=30, choices=SPECIES_LIST_CHOICES, default=SPECIES_LIST_NONE) def __str__(self): return '{0} - v{1}'.format(self.name, self.version) class Meta: app_label = 'wildlifecompliance' unique_together = ('name', 'version') @property def resources(self): return self.data_descriptor.get('resources', []) @property def with_application_species(self): ''' Boolean property to indicate that this return type is for a list of species selected when applying for licence. ''' with_application_species = False if self.species_list == self.SPECIES_LIST_APPLICATION: with_application_species = True return with_application_species @property def with_regulated_species(self): ''' Boolean property to indicate that this return type is for a list of regulated species applied from the questions answered when applying. ''' with_regulated_species = False if self.species_list == self.SPECIES_LIST_REGULATED: with_regulated_species = True return with_regulated_species @property def with_no_species(self): ''' Boolean property to indicate that this return type has no species associated. ''' no_species = False if self.species_list == self.SPECIES_LIST_NONE: no_species = True return no_species def get_resource_by_name(self, name): for resource in self.resources: if resource.get('name') == name: return resource return None def get_schema_by_name(self, name): resource = self.get_resource_by_name(name) return resource.get('schema', {}) if resource else None
class SessaoPlenaria(models.Model): FIELDFILE_NAME = ('upload_pauta', 'upload_ata', 'upload_anexo') metadata = JSONField(verbose_name=_('Metadados'), blank=True, null=True, default=None, encoder=DjangoJSONEncoder) cod_andamento_sessao = models.PositiveIntegerField(blank=True, null=True) painel_aberto = models.BooleanField(blank=True, default=False, verbose_name=_('Painel está aberto?')) tipo = models.ForeignKey(TipoSessaoPlenaria, on_delete=models.PROTECT, verbose_name=_('Tipo')) sessao_legislativa = models.ForeignKey( SessaoLegislativa, on_delete=models.CASCADE, verbose_name=_('Sessão Legislativa')) legislatura = models.ForeignKey(Legislatura, on_delete=models.PROTECT, verbose_name=_('Legislatura')) # XXX seems to be empty data_inicio = models.DateField(verbose_name=_('Abertura')) hora_inicio = models.CharField(max_length=5, verbose_name=_('Horário (hh:mm)')) hora_fim = models.CharField(max_length=5, blank=True, verbose_name=_('Horário (hh:mm)')) numero = models.PositiveIntegerField(verbose_name=_('Número')) data_fim = models.DateField(blank=True, null=True, verbose_name=_('Encerramento')) url_audio = models.URLField( max_length=150, blank=True, verbose_name=_('URL Arquivo Áudio (Formatos MP3 / AAC)')) url_video = models.URLField( max_length=150, blank=True, verbose_name=_('URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)')) upload_pauta = PortalFileField(blank=True, null=True, upload_to=pauta_upload_path, verbose_name=_('Pauta da Sessão'), storage=OverwriteStorage(), validators=[restringe_tipos_de_arquivo_txt]) upload_ata = PortalFileField(blank=True, null=True, upload_to=ata_upload_path, storage=OverwriteStorage(), verbose_name=_('Ata da Sessão'), validators=[restringe_tipos_de_arquivo_txt]) upload_anexo = PortalFileField(blank=True, null=True, storage=OverwriteStorage(), upload_to=anexo_upload_path, verbose_name=_('Anexo da Sessão')) iniciada = models.NullBooleanField(blank=True, choices=YES_NO_CHOICES, verbose_name=_('Sessão iniciada?'), default=True) finalizada = models.NullBooleanField(blank=True, choices=YES_NO_CHOICES, verbose_name=_('Sessão finalizada?'), default=False) selo_votacao_adicionado = models.NullBooleanField( blank=True, choices=YES_NO_CHOICES, verbose_name=_('Selo de Votação Adicionado?'), default=False) interativa = models.NullBooleanField(blank=True, choices=YES_NO_CHOICES, verbose_name=_('Sessão interativa')) tema_solene = models.TextField(blank=True, max_length=500, verbose_name=_('Tema da Sessão Solene')) data_ultima_atualizacao = models.DateTimeField(blank=True, null=True, auto_now=True, verbose_name=_('Data')) _certidao = GenericRelation(CertidaoPublicacao, related_query_name='sessaoplenaria_cert') _diario = GenericRelation(VinculoDocDiarioOficial, related_query_name='sessaoplenaria_diario') class Meta: verbose_name = _('Sessão Plenária') verbose_name_plural = _('Sessões Plenárias') @property def __descr__(self): return str(self) @property def certidao(self): return self._certidao.all().first() @property def diariooficial(self): try: return self._diario.all().first().diario except: return None def __str__(self): tnc = self.tipo.TIPO_NUMERACAO_CHOICES base = '{}ª {}'.format(self.numero, self.tipo.nome) if self.tipo.tipo_numeracao == tnc.quizenal: base += ' da {}ª Quinzena'.format( 1 if self.data_inicio.day <= 15 else 2) if self.tipo.tipo_numeracao <= tnc.mensal: base += ' do mês de {}'.format( formats.date_format(self.data_inicio, 'F')) if self.tipo.tipo_numeracao <= tnc.anual: base += ' de {}'.format(self.data_inicio.year) if self.tipo.tipo_numeracao <= tnc.sessao_legislativa: base += ' da {}ª Sessão Legislativa'.format( self.sessao_legislativa.numero) if self.tipo.tipo_numeracao <= tnc.legislatura: base += ' da {}ª Legislatura'.format(self.legislatura.numero) return base """return _('%(numero)sª Sessão %(tipo_nome)s' ' da %(sessao_legislativa_numero)sª Sessão Legislativa' ' da %(legislatura_id)sª Legislatura') % { 'numero': self.numero, 'tipo_nome': self.tipo.nome, 'sessao_legislativa_numero': self.sessao_legislativa.numero, # XXX check if it shouldn't be legislatura.numero 'legislatura_id': self.legislatura.numero} """ def str_short(self): tnc = self.tipo.TIPO_NUMERACAO_CHOICES base = '{}ª {}'.format(self.numero, self.tipo.nome) if self.tipo.tipo_numeracao == tnc.quizenal: base += ' da {}ª Quinzena'.format( 1 if self.data_inicio.day <= 15 else 2) if self.tipo.tipo_numeracao <= tnc.mensal: base += ' do mês de {}'.format( formats.date_format(self.data_inicio, 'F')) if self.tipo.tipo_numeracao <= tnc.anual: base += ' de {}'.format(self.data_inicio.year) return base def str_title(self): return self.str_short() def str_subtitle(self): tnc = self.tipo.TIPO_NUMERACAO_CHOICES base = '' if self.tipo.tipo_numeracao <= tnc.sessao_legislativa: base = '{}ª Sessão Legislativa'.format( self.sessao_legislativa.numero) if self.tipo.tipo_numeracao <= tnc.legislatura: base += ' da {}ª Legislatura'.format(self.legislatura.numero) return base def delete(self, using=None, keep_parents=False): if self.upload_pauta: self.upload_pauta.delete() if self.upload_ata: self.upload_ata.delete() if self.upload_anexo: self.upload_anexo.delete() return models.Model.delete(self, using=using, keep_parents=keep_parents) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.pk and (self.upload_pauta or self.upload_ata or self.upload_anexo): upload_pauta = self.upload_pauta upload_ata = self.upload_ata upload_anexo = self.upload_anexo self.upload_pauta = None self.upload_ata = None self.upload_anexo = None models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) self.upload_pauta = upload_pauta self.upload_ata = upload_ata self.upload_anexo = upload_anexo return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) @property def ano(self): return self.data_inicio.year
class Gathering(TimeStampedModel, SoftDeletableModel, Utility): notes = GenericRelation(Note) id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID') meet = models.ForeignKey('Meet', on_delete=models.SET(0), null=False, blank=False) start = models.DateTimeField(null=False, blank=False) finish = models.DateTimeField( null=False, blank=False, help_text="Required for user to filter by time") attendings = models.ManyToManyField('persons.Attending', through='Attendance') display_name = models.CharField(max_length=50, blank=True, null=True, help_text="02/09/2020, etc") infos = JSONField( null=True, blank=True, default=dict, help_text= 'Example: {"LG_location": "F207", "link": "https://..."}. Please keep {} here even no data' ) occurrence = models.ForeignKey(Occurrence, blank=True, null=True, on_delete=models.SET_NULL) site_type = models.ForeignKey( ContentType, on_delete=models.SET(0), help_text='site: django_content_type id for table name') site_id = models.BigIntegerField() site = GenericForeignKey('site_type', 'site_id') # from itertools import groupby # from operator import attrgetter # # ordered_program_sessions = ProgramSession.objects.order_by('program_group', 'start_at') # program_sessions_grouped_by_program_groups = { # k: list(v) # for k, v in groupby(ordered_program_sessions, attrgetter('program_group')) # } #=> {<ProgramGroup: The Rock >: [<ProgramSession: The Rock #1...>, <ProgramSession: The Rock #2...>]} def get_absolute_url(self): return reverse('gathering_detail', args=[str(self.id)]) @property def gathering_label(self): return (self.meet.display_name or '') + ' ' + (self.display_name or '') class Meta: db_table = 'occasions_gatherings' ordering = ['meet', 'start'] constraints = [ models.UniqueConstraint( fields=['meet_id', 'site_type_id', 'site_id', 'start'], condition=models.Q(is_removed=False), name='uniq_meet_location_time') ] def __str__(self): return '%s %s %s %s' % (self.meet, self.start, self.display_name or '', self.site or '')
def get_prep_value(self, value): if value is not None: return Json(value, dumps=partial(json.dumps, cls=DjangoJSONEncoder)) return value def get_prep_lookup(self, lookup_type, value): if lookup_type in ('has_key', 'has_keys', 'has_any_keys'): return value if isinstance(value, (dict, list)): return Json(value, dumps=partial(json.dumps, cls=DjangoJSONEncoder)) return super(JSONField, self).get_prep_lookup(lookup_type, value) def validate(self, value, model_instance): super(JSONField, self).validate(value, model_instance) try: json.dumps(value, cls=DjangoJSONEncoder) except TypeError: raise exceptions.ValidationError( self.error_messages['invalid'], code='invalid', params={'value': value}, ) JSONField.register_lookup(lookups.DataContains) JSONField.register_lookup(lookups.ContainedBy) JSONField.register_lookup(lookups.HasKey) JSONField.register_lookup(lookups.HasKeys) JSONField.register_lookup(lookups.HasAnyKeys)
class Approval(RevisionedMixin): STATUS_CURRENT = 'current' STATUS_EXPIRED = 'expired' STATUS_CANCELLED = 'cancelled' STATUS_SURRENDERED = 'surrendered' STATUS_SUSPENDED = 'suspended' STATUS_CHOICES = ((STATUS_CURRENT, 'Current'), (STATUS_EXPIRED, 'Expired'), (STATUS_CANCELLED, 'Cancelled'), (STATUS_SURRENDERED, 'Surrendered'), (STATUS_SUSPENDED, 'Suspended')) lodgement_number = models.CharField(max_length=9, blank=True, default='') status = models.CharField(max_length=40, choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0]) # NB: licence_document not used for Apiary applications licence_document = models.ForeignKey(ApprovalDocument, blank=True, null=True, related_name='licence_document') cover_letter_document = models.ForeignKey( ApprovalDocument, blank=True, null=True, related_name='cover_letter_document') replaced_by = models.ForeignKey('self', blank=True, null=True) #current_proposal = models.ForeignKey(Proposal,related_name = '+') current_proposal = models.ForeignKey(Proposal, related_name='approvals') renewal_document = models.ForeignKey(ApprovalDocument, blank=True, null=True, related_name='renewal_document') # apiary_renewal_document = models.ForeignKey(RenewalDocument, blank=True, null=True, related_name='apiary_renewal_document') renewal_sent = models.BooleanField(default=False) issue_date = models.DateTimeField() original_issue_date = models.DateField(auto_now_add=True) start_date = models.DateField() expiry_date = models.DateField() surrender_details = JSONField(blank=True, null=True) suspension_details = JSONField(blank=True, null=True) applicant = models.ForeignKey(Organisation, on_delete=models.PROTECT, blank=True, null=True, related_name='disturbance_approvals') proxy_applicant = models.ForeignKey( EmailUser, on_delete=models.PROTECT, blank=True, null=True, related_name='disturbance_proxy_approvals') extracted_fields = JSONField(blank=True, null=True) cancellation_details = models.TextField(blank=True) cancellation_date = models.DateField(blank=True, null=True) set_to_cancel = models.BooleanField(default=False) set_to_suspend = models.BooleanField(default=False) set_to_surrender = models.BooleanField(default=False) reissued = models.BooleanField(default=False) apiary_approval = models.BooleanField(default=False) no_annual_rental_fee_until = models.DateField(blank=True, null=True) apiary_sites = models.ManyToManyField('ApiarySite', through=ApiarySiteOnApproval, related_name='approval_set') migrated = models.BooleanField(default=False) class Meta: app_label = 'disturbance' unique_together = ('lodgement_number', 'issue_date') def add_apiary_sites_to_proposal_apiary_for_renewal(self, proposal_apiary): for apiary_site in self.apiary_sites.all( ): # Exclude just in case there is. relation = self.get_relation(apiary_site) ApiarySiteOnProposal.objects.create( apiary_site=apiary_site, proposal_apiary=proposal_apiary, wkb_geometry_draft=relation.wkb_geometry, site_category_draft=relation.site_category, for_renewal=True, ) def get_relation(self, apiary_site): if isinstance(apiary_site, dict): apiary_site = ApiarySite.objects.get(id=apiary_site.get('id')) relation_obj = ApiarySiteOnApproval.objects.get( apiary_site=apiary_site, approval=self) return relation_obj def get_relations(self): # relation_objs = ApiarySiteOnApproval.objects.filter(apiary_site__in=self.apiary_sites.all(), approval=self) relation_objs = ApiarySiteOnApproval.objects.filter( apiary_site__in=self.apiary_sites.all(), approval=self).exclude(site_status=SITE_STATUS_TRANSFERRED) return relation_objs @property def get_current_apiary_sites(self): # relation_objs = ApiarySiteOnApproval.objects.filter(apiary_site__in=self.apiary_sites.all(), approval=self) relation_objs = ApiarySiteOnApproval.objects.filter( apiary_site__in=self.apiary_sites.all(), approval=self).filter(site_status=SITE_STATUS_CURRENT) return relation_objs @property def relevant_renewal_document(self): if self.apiary_approval: # return self.renewal_documents.filter(expiry_date=self.expiry_date).first() if self.renewal_documents.filter(expiry_date=self.expiry_date) else None temp = self.renewal_documents.last() all = self.renewal_documents.all() return self.renewal_documents.last() if self.renewal_documents.all( ) else None else: return self.renewal_document # return self.apiary_renewal_document if self.apiary_renewal_document else self.renewal_document @property def relevant_applicant_id(self): if self.applicant: #return self.org_applicant.organisation.id return self.applicant.id elif self.proxy_applicant: return self.proxy_applicant.id @property def relevant_applicant(self): if self.applicant: return self.applicant else: return self.proxy_applicant @property def relevant_applicant_email_user(self): if self.applicant: return self.applicant.delegates.all()[0] else: return self.proxy_applicant @property def relevant_applicant_email(self): if self.applicant and hasattr( self.applicant.organisation, 'email') and self.applicant.organisation.email: return self.applicant.organisation.email elif self.proxy_applicant: return self.proxy_applicant.email else: return self.current_proposal.submitter.email @property def relevant_applicant_name(self): if self.applicant: return self.applicant.name elif self.proxy_applicant: return self.proxy_applicant.get_full_name() else: return self.current_proposal.submitter.get_full_name() @property def relevant_applicant_address(self): if self.applicant: return self.applicant.address elif self.proxy_applicant: #return self.proxy_applicant.addresses.all().first() return self.proxy_applicant.residential_address else: return self.current_proposal.submitter.residential_address @property def region(self): try: return self.current_proposal.region.name except: return '' @property def district(self): try: return self.current_proposal.district.name except: return '' @property def tenure(self): try: return self.current_proposal.tenure.name except: return '' @property def activity(self): return self.current_proposal.activity @property def title(self): return self.current_proposal.title @property def next_id(self): #ids = map(int,[(i.lodgement_number.split('A')[1]) for i in Approval.objects.all()]) ids = map(int, [ i.split('A')[1] for i in Approval.objects.all().values_list('lodgement_number', flat=True) if i ]) ids = list( ids ) # In python 3, map returns map object. Therefore before 'if ids' it should be converted to the list(/tuple,...) otherwise 'if ids' is always True return max(ids) + 1 if ids else 1 def save(self, *args, **kwargs): super(Approval, self).save(*args, **kwargs) if self.lodgement_number == '': self.lodgement_number = 'A{0:06d}'.format(self.next_id) self.save() def __str__(self): return self.lodgement_number @property def reference(self): return 'A{}'.format(self.id) @property def can_reissue(self): return self.status == Approval.STATUS_CURRENT or self.status == Approval.STATUS_SUSPENDED @property def can_reinstate(self): return self.status in (Approval.STATUS_CANCELLED, Approval.STATUS_SUSPENDED, Approval.STATUS_SURRENDERED) and self.can_action @property def allowed_assessors(self): return self.current_proposal.compliance_assessors @property def allowed_approvers(self): return self.current_proposal.allowed_approvers @property def is_issued(self): return self.licence_number is not None and len(self.licence_number) > 0 @property def can_action(self): if not (self.set_to_cancel or self.set_to_suspend or self.set_to_surrender): return True else: return False @property def can_renew(self): if self.apiary_approval: open_renewal_proposal_exists = False # check for "open" renewal proposals for proposal in self.proposal_set.all(): if (proposal.proposal_type == 'renewal' and proposal.processing_status not in [ proposal.PROCESSING_STATUS_APPROVED, proposal.PROCESSING_STATUS_DECLINED, proposal.PROCESSING_STATUS_DISCARDED ]): open_renewal_proposal_exists = True if self.renewal_sent and not open_renewal_proposal_exists: # Renewal notification has been sent, but the current proposal is not for the renewal return True else: return False else: try: renew_conditions = { 'previous_application': self.current_proposal, 'proposal_type': 'renewal' } proposal = Proposal.objects.get(**renew_conditions) if proposal: # Proposal for the renewal already exists. return False except Proposal.DoesNotExist: # Proposal for the renewal doesn't exit return True @property def can_amend(self): if not self.apiary_approval: try: amend_conditions = { 'previous_application': self.current_proposal, 'proposal_type': 'amendment' } proposal = Proposal.objects.get(**amend_conditions) if proposal: return False except Proposal.DoesNotExist: if self.can_renew: return True else: return False def generate_apiary_site_transfer_doc(self, request_user, site_transfer_proposal, preview=None): return self.generate_apiary_licence_doc(site_transfer_proposal, request_user, preview) # copied_to_permit = self.copiedToPermit_fields(site_transfer_proposal) # Get data related to isCopiedToPermit tag # if preview: # pdf_contents = create_apiary_licence_pdf_contents(self, site_transfer_proposal, copied_to_permit, user) # return pdf_contents # self.licence_document = create_approval_document(self, site_transfer_proposal, copied_to_permit, user) # self.save(version_comment='Created Approval PDF: {}'.format(self.licence_document.name)) # self.current_proposal.save(version_comment='Created Approval PDF: {}'.format(self.licence_document.name)) def generate_doc(self, request_user, preview=None, site_transfer_preview=None): #if self.current_proposal and self.current_proposal.apiary_group_application_type: if self.apiary_approval: return self.generate_apiary_licence_doc(self.current_proposal, request_user, preview, site_transfer_preview) else: from disturbance.components.approvals.pdf import create_approval_doc, create_approval_pdf_bytes copied_to_permit = self.copiedToPermit_fields( self.current_proposal ) #Get data related to isCopiedToPermit tag if preview: return create_approval_pdf_bytes(self, self.current_proposal, copied_to_permit, request_user) self.licence_document = create_approval_doc( self, self.current_proposal, copied_to_permit, request_user) self.save(version_comment='Created Approval PDF: {}'.format( self.licence_document.name)) self.current_proposal.save( version_comment='Created Approval PDF: {}'.format( self.licence_document.name)) def generate_apiary_licence_doc(self, proposal, request_user, preview=None, site_transfer_preview=None): #import ipdb; ipdb.set_trace() copied_to_permit = self.copiedToPermit_fields( proposal) #Get data related to isCopiedToPermit tag if preview: pdf_contents = create_apiary_licence_pdf_contents( self, proposal, copied_to_permit, request_user, site_transfer_preview) return pdf_contents #self.licence_document = create_apiary_licence_pdf_contents(self, proposal, copied_to_permit, request_user) self.licence_document = create_approval_document( self, proposal, copied_to_permit, request_user) self.save(version_comment='Created Approval PDF: {}'.format( self.licence_document.name)) self.current_proposal.save(version_comment='Created Approval PDF: {}'. format(self.licence_document.name)) def generate_renewal_doc(self): from disturbance.components.approvals.pdf import create_renewal_doc, create_apiary_renewal_doc if self.apiary_approval: # self.apiary_renewal_document = create_apiary_renewal_doc(self,self.current_proposal) # self.save(version_comment='Created Approval PDF: {}'.format(self.apiary_renewal_document.name)) # self.current_proposal.save(version_comment='Created Approval PDF: {}'.format(self.apiary_renewal_document.name)) doc = create_apiary_renewal_doc(self, self.current_proposal) self.save( version_comment='Created Approval PDF: {}'.format(doc.name)) self.current_proposal.save( version_comment='Created Approval PDF: {}'.format(doc.name)) else: self.renewal_document = create_renewal_doc(self, self.current_proposal) self.save(version_comment='Created Approval PDF: {}'.format( self.renewal_document.name)) self.current_proposal.save( version_comment='Created Approval PDF: {}'.format( self.renewal_document.name)) def copiedToPermit_fields(self, proposal): p = proposal copied_data = [] search_assessor_data = [] search_schema = search_multiple_keys(p.schema, primary_search='isCopiedToPermit', search_list=['label', 'name']) if p.assessor_data: search_assessor_data = search_keys( p.assessor_data, search_list=['assessor', 'name']) if search_schema: for c in search_schema: try: if search_assessor_data: for d in search_assessor_data: if c['name'] == d['name']: if d['assessor']: #copied_data.append({c['label'], d['assessor']}) copied_data.append( {c['label']: d['assessor']}) except: raise return copied_data def log_user_action(self, action, request): return ApprovalUserAction.log_action(self, action, request.user) def expire_approval(self, user): with transaction.atomic(): try: today = timezone.localtime(timezone.now()).date() if self.status == Approval.STATUS_CURRENT and self.expiry_date < today: self.status = Approval.STATUS_EXPIRED self.save() send_approval_expire_email_notification(self) # Change the statuses of the apiary sites, too self.change_apiary_site_status(self.status) proposal = self.current_proposal ApprovalUserAction.log_action( self, ApprovalUserAction.ACTION_EXPIRE_APPROVAL.format( self.lodgement_number), user) ProposalUserAction.log_action( proposal, ProposalUserAction.ACTION_EXPIRED_APPROVAL_.format( proposal.lodgement_number), user) except: raise def change_apiary_site_status(self, approval_status): relations = self.get_relations() if approval_status in ( Approval.STATUS_CANCELLED, Approval.STATUS_SUSPENDED, Approval.STATUS_SURRENDERED, ): relations.update(site_status=SITE_STATUS_NOT_TO_BE_REISSUED) elif approval_status == Approval.STATUS_EXPIRED: for apiary_site in self.apiary_sites.all(): apiary_site.make_vacant(True, apiary_site.latest_approval_link) relations.update(available=False) elif approval_status == Approval.STATUS_CURRENT: relations.update(site_status=SITE_STATUS_CURRENT) def approval_cancellation(self, request, details): with transaction.atomic(): try: if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to cancel this approval') if not self.can_reissue and self.can_action: raise ValidationError( 'You cannot cancel approval if it is not current or suspended' ) self.cancellation_date = details.get( 'cancellation_date').strftime('%Y-%m-%d') self.cancellation_details = details.get('cancellation_details') cancellation_date = datetime.datetime.strptime( self.cancellation_date, '%Y-%m-%d') cancellation_date = cancellation_date.date() self.cancellation_date = cancellation_date today = timezone.now().date() if cancellation_date <= today: if not self.status == Approval.STATUS_CANCELLED: self.status = Approval.STATUS_CANCELLED self.set_to_cancel = False send_approval_cancel_email_notification(self) # Change the statuses of the apiary sites, too self.change_apiary_site_status(self.status) else: self.set_to_cancel = True send_approval_cancel_email_notification(self, future_cancel=True) #import ipdb; ipdb.set_trace() self.save() # Log proposal action self.log_user_action( ApprovalUserAction.ACTION_CANCEL_APPROVAL.format( self.lodgement_number), request) # Log entry for organisation #self.current_proposal.log_user_action(ProposalUserAction.ACTION_CANCEL_APPROVAL.format(self.current_proposal.id),request) except: raise def approval_suspension(self, request, details): with transaction.atomic(): try: if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to suspend this approval') if not self.can_reissue and self.can_action: raise ValidationError( 'You cannot suspend approval if it is not current or suspended' ) if details.get('to_date'): to_date = details.get('to_date').strftime('%d/%m/%Y') else: to_date = '' self.suspension_details = { 'from_date': details.get('from_date').strftime('%d/%m/%Y'), 'to_date': to_date, 'details': details.get('suspension_details'), } today = timezone.now().date() from_date = datetime.datetime.strptime( self.suspension_details['from_date'], '%d/%m/%Y') from_date = from_date.date() if from_date <= today: if not self.status == Approval.STATUS_SUSPENDED: self.status = Approval.STATUS_SUSPENDED self.set_to_suspend = False self.save() send_approval_suspend_email_notification(self) # Change the statuses of the apiary sites, too self.change_apiary_site_status(self.status) else: self.set_to_suspend = True send_approval_suspend_email_notification( self, future_suspend=True) self.save() # Log approval action self.log_user_action( ApprovalUserAction.ACTION_SUSPEND_APPROVAL.format( self.lodgement_number), request) # Log entry for proposal #self.current_proposal.log_user_action(ProposalUserAction.ACTION_SUSPEND_APPROVAL.format(self.current_proposal.id),request) except: raise def reinstate_approval(self, request): with transaction.atomic(): try: if not request.user in self.allowed_assessors: raise ValidationError( 'You do not have access to reinstate this approval') if not self.can_reinstate: #if not self.status == 'suspended': raise ValidationError( 'You cannot reinstate approval at this stage') today = timezone.now().date() if not self.can_reinstate and self.expiry_date >= today: #if not self.status == 'suspended' and self.expiry_date >= today: raise ValidationError( 'You cannot reinstate approval at this stage') if self.status == Approval.STATUS_CANCELLED: self.cancellation_details = '' self.cancellation_date = None if self.status == Approval.STATUS_SURRENDERED: self.surrender_details = {} if self.status == Approval.STATUS_SUSPENDED: self.suspension_details = {} self.status = Approval.STATUS_CURRENT #self.suspension_details = {} self.save() send_approval_reinstate_email_notification(self, request) # Change the statuses of the apiary sites, too self.change_apiary_site_status(self.status) # Log approval action self.log_user_action( ApprovalUserAction.ACTION_REINSTATE_APPROVAL.format( self.lodgement_number), request) # Log entry for proposal #self.current_proposal.log_user_action(ProposalUserAction.ACTION_REINSTATE_APPROVAL.format(self.current_proposal.id),request) except: raise def approval_surrender(self, request, details): with transaction.atomic(): try: if self.applicant and not request.user.disturbance_organisations.filter( organisation_id=self.relevant_applicant_id): #if not request.user in self.allowed_assessors: if request.user not in self.allowed_assessors and not is_customer( request): raise ValidationError( 'You do not have access to surrender this approval' ) if not self.can_reissue and self.can_action: raise ValidationError( 'You cannot surrender approval if it is not current or suspended' ) self.surrender_details = { 'surrender_date': details.get('surrender_date').strftime('%d/%m/%Y'), 'details': details.get('surrender_details'), } today = timezone.now().date() surrender_date = datetime.datetime.strptime( self.surrender_details['surrender_date'], '%d/%m/%Y') surrender_date = surrender_date.date() if surrender_date <= today: if not self.status == Approval.STATUS_SURRENDERED: self.status = Approval.STATUS_SURRENDERED self.set_to_surrender = False self.save() send_approval_surrender_email_notification(self) # Change the statuses of the apiary sites, too self.change_apiary_site_status(self.status) else: self.set_to_surrender = True send_approval_surrender_email_notification( self, future_surrender=True) self.save() # Log approval action self.log_user_action( ApprovalUserAction.ACTION_SURRENDER_APPROVAL.format( self.lodgement_number), request) # Log entry for proposal #self.current_proposal.log_user_action(ProposalUserAction.ACTION_SURRENDER_APPROVAL.format(self.current_proposal.id),request) except: raise def pdf_view_log(self, request): self.log_user_action( ApprovalUserAction.ACTION_APPROVAL_PDF_VIEW.format( self.lodgement_number), request) return self