# coding: utf-8 import hashlib from datetime import datetime from django.db import models from django.core.urlresolvers import reverse from django.utils.encoding import smart_str, smart_unicode from django.utils.functional import cached_property class TimeStampedModel(models.Model): """ An abstract base class model that provides self-updating ``created`` and ``modified`` fields. """ created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) class Meta: abstract = True class HashableModel(models.Model): """ An abstract base class model that provides a fingerprint field """ fingerprint = models.CharField( max_length=40, unique=True, ) class Meta: abstract = True def calculate_hash(self): fingerprint = hashlib.sha1() for field_name in self.hashable_fields: field = self._meta.get_field(field_name) if field.is_relation: related = getattr(self, field_name) if related is None: fingerprint.update(smart_str(related)) else: fingerprint.update(related.fingerprint) else: fingerprint.update( smart_str(getattr(self, field_name)) ) self.fingerprint = fingerprint.hexdigest() return self.fingerprint def get_hash_str(self): string = '' for field_name in self.hashable_fields: field = self._meta.get_field(field_name) if field.is_relation: string += getattr(self, field_name).fingerprint else: string += smart_str(getattr(self, field_name)) return string def save(self, *args, **kwargs): self.calculate_hash() super(HashableModel, self).save(*args, **kwargs) class Country(models.Model): name = models.CharField(max_length=255) code = models.CharField(max_length=2, unique=True) @property def fingerprint(self): fingerprint = hashlib.sha1() fingerprint.update(smart_str(self.name)) fingerprint.update(smart_str(self.code)) return fingerprint.hexdigest() def __unicode__(self): return u'{} [{}]'.format(self.name, self.code) def get_absolute_url(self): return reverse('representatives:representative-list', kwargs=dict(group_kind='country', group=self.name)) class Representative(HashableModel, TimeStampedModel): """ Base model for representatives """ slug = models.SlugField(max_length=100) remote_id = models.CharField(max_length=255, unique=True) first_name = models.CharField(max_length=255, blank=True, default='') last_name = models.CharField(max_length=255, blank=True, default='') full_name = models.CharField(max_length=255) GENDER = ( (0, "N/A"), (1, "F"), (2, "M"), ) gender = models.SmallIntegerField(choices=GENDER, default=0) birth_place = models.CharField(max_length=255, blank=True, default='') birth_date = models.DateField(blank=True, null=True) cv = models.TextField(blank=True, default='') photo = models.CharField(max_length=512, null=True) active = models.BooleanField(default=False) hashable_fields = ['remote_id'] def __unicode__(self): return u'{} ({})'.format(smart_unicode(self.full_name), self.remote_id) def gender_as_str(self): genders = {0: 'N/A', 1: 'F', 2: 'M'} return genders[self.gender] class Meta: ordering = ['last_name', 'first_name'] def get_absolute_url(self): return reverse('representatives:representative-detail', args=(self.slug,)) # Contact related models class Contact(TimeStampedModel): representative = models.ForeignKey(Representative) class Meta: abstract = True class Email(Contact): email = models.EmailField() kind = models.CharField(max_length=255, blank=True, default='') class WebSite(Contact): url = models.CharField(max_length=2048, blank=True, default='') kind = models.CharField(max_length=255, blank=True, default='') class Address(Contact): country = models.ForeignKey(Country) city = models.CharField(max_length=255, blank=True, default='') street = models.CharField(max_length=255, blank=True, default='') number = models.CharField(max_length=255, blank=True, default='') postcode = models.CharField(max_length=255, blank=True, default='') floor = models.CharField(max_length=255, blank=True, default='') office_number = models.CharField(max_length=255, blank=True, default='') kind = models.CharField(max_length=255, blank=True, default='') name = models.CharField(max_length=255, blank=True, default='') location = models.CharField(max_length=255, blank=True, default='') class Phone(Contact): number = models.CharField(max_length=255, blank=True, default='') kind = models.CharField(max_length=255, blank=True, default='') address = models.ForeignKey(Address, null=True, related_name='phones') class Chamber(HashableModel): """ A representative chamber """ name = models.CharField(max_length=255) country = models.ForeignKey('Country', null=True, related_name='chambers') abbreviation = models.CharField(max_length=10, blank=True, default='', db_index=True) hashable_fields = ['name', 'country', 'abbreviation'] def __unicode__(self): return u'{} [{}]'.format(self.name, self.abbreviation) class Group(HashableModel, TimeStampedModel): """ An entity represented by a representative through a mandate """ name = models.CharField(max_length=255, db_index=True) abbreviation = models.CharField(max_length=10, blank=True, default='', db_index=True) kind = models.CharField(max_length=255, db_index=True) chamber = models.ForeignKey(Chamber, null=True, related_name='groups') hashable_fields = ['name', 'abbreviation', 'kind', 'chamber'] @cached_property def active(self): return self.mandates.filter(end_date__gte=datetime.now()).exists() def __unicode__(self): return unicode(self.name) class Meta: ordering = ('name',) def get_absolute_url(self): if self.chamber: return reverse('representatives:representative-list', kwargs=dict(group_kind=self.kind, chamber=self.chamber.name, group=self.name)) else: return reverse('representatives:representative-list', kwargs=dict(group_kind=self.kind, group=self.name)) class Constituency(HashableModel, TimeStampedModel): """ An authority for which a representative has a mandate """ name = models.CharField(max_length=255) country = models.ForeignKey('Country', null=True, blank=True, related_name='constituencies') hashable_fields = ['name'] @cached_property def active(self): return self.mandates.filter(end_date__gte=datetime.now()).exists() def __unicode__(self): return unicode(self.name) class MandateManager(models.Manager): """ This satisfies repr(Mandate) """ def get_queryset(self): return super( MandateManager, self).get_queryset().select_related( 'group', 'constituency') class Mandate(HashableModel, TimeStampedModel): objects = MandateManager() group = models.ForeignKey(Group, null=True, related_name='mandates') constituency = models.ForeignKey( Constituency, null=True, related_name='mandates') representative = models.ForeignKey(Representative, related_name='mandates') role = models.CharField( max_length=25, blank=True, default='', help_text="Eg.: president of a political group" ) begin_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) link = models.URLField() hashable_fields = ['group', 'constituency', 'role', 'begin_date', 'end_date', 'representative'] @property def active(self): return self.end_date >= datetime.now().date() def __unicode__(self): t = u'Mandate : {representative},{role} {group} for {constituency}' return t.format( representative=self.representative, role=( u' {} of'.format( self.role) if self.role else u''), constituency=self.constituency, group=self.group) class Meta: ordering = ('-end_date',)