#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010-2013 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # See the file COPYING for details. """ Models description """ from cStringIO import StringIO import datetime from PIL import Image import os import tempfile from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.core.validators import validate_slug from django.core.urlresolvers import reverse, NoReverseMatch from django.db.utils import DatabaseError from django.db.models import Q, Max, Count from django.db.models.signals import post_save from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.safestring import SafeUnicode, mark_safe from django.template.defaultfilters import slugify from django.contrib.auth.models import User, Group from django.contrib.gis.db import models from django.contrib import admin from simple_history.models import HistoricalRecords as BaseHistoricalRecords from ooo_replace import ooo_replace def post_save_user(sender, **kwargs): user = kwargs['instance'] ishtaruser = None q = IshtarUser.objects.filter(username=user.username) if not q.count(): ishtaruser = IshtarUser.create_from_user(user) else: ishtaruser = q.all()[0] if ishtaruser.is_superuser \ and not ishtaruser.has_right('administrator'): ishtaruser.person.person_types.add(PersonType.objects.get( txt_idx='administrator')) post_save.connect(post_save_user, sender=User) class ValueGetter(object): _prefix = "" def get_values(self, prefix=''): if not prefix: prefix = self._prefix values = {} for field_name in self._meta.get_all_field_names(): if not hasattr(self, field_name): continue value = getattr(self, field_name) if hasattr(value, 'get_values'): values.update(value.get_values(prefix + field_name + '_')) else: values[prefix + field_name] = value return values @classmethod def get_empty_values(cls, prefix=''): if not prefix: prefix = self._prefix values = {} for field_name in cls._meta.get_all_field_names(): values[prefix + field_name] = '' return values class HistoricalRecords(BaseHistoricalRecords): def create_historical_record(self, instance, type): history_modifier = getattr(instance, 'history_modifier', None) manager = getattr(instance, self.manager_name) attrs = {} for field in instance._meta.fields: attrs[field.attname] = getattr(instance, field.attname) q_history = instance.history.filter( history_modifier_id=history_modifier.pk ).order_by('-history_date', '-history_id') if not q_history.count(): manager.create(history_type=type, **attrs) return old_instance = q_history.all()[0] # multiple saving by the same user in a very short time are generaly # caused by post_save signals it is not relevant to keep them min_history_date = datetime.datetime.now() \ - datetime.timedelta(seconds=5) q = q_history.filter(history_date__isnull=False, history_date__gt=min_history_date ).order_by('-history_date', '-history_id') if q.count(): return # record a new version only if data have been changed for field in instance._meta.fields: if getattr(old_instance, field.attname) != attrs[field.attname]: manager.create(history_type=type, **attrs) return # valid ID validator for models def valid_id(cls): def func(value): try: cls.objects.get(pk=value) except ObjectDoesNotExist: raise ValidationError(_(u"Not a valid item.")) return func def valid_ids(cls): def func(value): if "," in value: value = value.split(",") for v in value: try: cls.objects.get(pk=v) except ObjectDoesNotExist: raise ValidationError( _(u"An item selected is not a valid item.")) return func # unique validator for models def is_unique(cls, field): def func(value): query = {field:value} try: assert cls.objects.filter(**query).count() == 0 except AssertionError: raise ValidationError(_(u"This item already exist.")) return func class OwnPerms: """ Manage special permissions for object's owner """ @classmethod def get_query_owns(cls, user): """ Query object to get own items """ return None # implement for each object def is_own(self, user): """ Check if the current object is owned by the user """ query = self.get_query_owns(user) if not query: return False query = query & Q(pk=self.pk) return cls.objects.filter(query).count() @classmethod def has_item_of(cls, user): """ Check if the user own some items """ query = cls.get_query_owns(user) if not query: return False return cls.objects.filter(query).count() @classmethod def get_owns(cls, user): """ Get Own items """ if isinstance(user, User): user = IshtarUser.objects.get(user_ptr=user) if user.is_anonymous(): return [] query = cls.get_query_owns(user) if not query: return [] return cls.objects.filter(query).order_by(*cls._meta.ordering).all() class GeneralType(models.Model): """ Abstract class for "types" """ label = models.CharField(_(u"Label"), max_length=100) txt_idx = models.CharField(_(u"Textual ID"), validators=[validate_slug], max_length=30, unique=True) comment = models.TextField(_(u"Comment"), blank=True, null=True) available = models.BooleanField(_(u"Available")) HELP_TEXT = u"" class Meta: abstract = True unique_together = (('txt_idx', 'available'),) def __unicode__(self): return self.label @property def short_label(self): return self.label @classmethod def get_help(cls, dct={}, exclude=[]): help_text = cls.HELP_TEXT c_rank = -1 help_items = u"\n" for item in cls.get_types(dct=dct, instances=True, exclude=exclude): if hasattr(item, '__iter__'): # TODO: manage multiple levels continue if not item.comment: continue if c_rank > item.rank: help_items += u"\n" elif c_rank < item.rank: help_items += u"
\n" c_rank = item.rank help_items += u"
%s
%s
" % (item.label, u"
".join(item.comment.split('\n'))) c_rank += 1 if c_rank: help_items += c_rank*u"
" if help_text or help_items != u'\n': return mark_safe(help_text + help_items) return u"" @classmethod def get_types(cls, dct={}, instances=False, exclude=[], empty_first=True, default=None): base_dct = dct.copy() if hasattr(cls, 'parent'): return cls._get_parent_types(base_dct, instances, exclude=exclude, empty_first=empty_first, default=default) return cls._get_types(base_dct, instances, exclude=exclude, empty_first=empty_first, default=default) @classmethod def _get_types(cls, dct={}, instances=False, exclude=[], empty_first=True, default=None): dct['available'] = True if not instances and empty_first and not default: yield ('', '--') if default: try: default = cls.objects.get(txt_idx=default) yield(default.pk, _(unicode(default))) except cls.DoesNotExist: pass items = cls.objects.filter(**dct) if default: exclude.append(default.txt_idx) if exclude: items = items.exclude(txt_idx__in=exclude) for item in items.order_by(*cls._meta.ordering).all(): if instances: item.rank = 0 yield item else: yield (item.pk, _(unicode(item))) PREFIX = "› " @classmethod def _get_childs(cls, item, dct, prefix=0, instances=False, exclude=[]): prefix += 1 dct['parent'] = item childs = cls.objects.filter(**dct) if exclude: childs = childs.exclude(txt_idx__in=exclude) if hasattr(cls, 'order'): childs = childs.order_by('order') for child in childs.all(): if instances: child.rank = prefix yield child else: yield (child.pk, SafeUnicode(prefix*cls.PREFIX + \ unicode(_(unicode(child))) )) for sub_child in cls._get_childs(child, dct, prefix, instances, exclude=exclude): yield sub_child @classmethod def _get_parent_types(cls, dct={}, instances=False, exclude=[], empty_first=True, default=None): dct['available'] = True if not instances and empty_first: yield ('', '--') dct['parent'] = None items = cls.objects.filter(**dct) if exclude: items = items.exclude(txt_idx__in=exclude) if hasattr(cls, 'order'): items = items.order_by('order') for item in items.all(): if instances: item.rank = 0 yield item else: yield (item.pk, unicode(item)) for child in cls._get_childs(item, dct, instances, exclude=exclude): yield child class ImageModel(models.Model): image = models.ImageField(upload_to="upload/", blank=True, null=True) thumbnail = models.ImageField(upload_to='upload/thumbs/', blank=True, null=True) IMAGE_MAX_SIZE = settings.IMAGE_MAX_SIZE THUMB_MAX_SIZE = settings.THUMB_MAX_SIZE class Meta: abstract = True def has_changed(self, field): if not self.pk: return True manager = getattr(self.__class__, 'objects') old = getattr(manager.get(pk=self.pk), field) return not getattr(self, field) == old def create_thumb(self, image, size): """Returns the image resized to fit inside a box of the given size""" image.thumbnail(size, Image.ANTIALIAS) temp = StringIO() image.save(temp, 'jpeg') temp.seek(0) return SimpleUploadedFile('temp', temp.read()) def save(self, *args, **kwargs): # manage images if self.has_changed('image') and self.image: # convert to jpg filename = os.path.splitext(os.path.split(self.image.name)[-1])[0] old_path = self.image.path filename = "%s.jpg" % filename image = Image.open(self.image.file) # convert to RGB if image.mode not in ('L', 'RGB'): image = image.convert('RGB') # resize if necessary self.image.save(filename, self.create_thumb(image, self.IMAGE_MAX_SIZE), save=False) if old_path != self.image.path: os.remove(old_path) # save the thumbnail self.thumbnail.save('_%s' % filename, self.create_thumb(image, self.THUMB_MAX_SIZE), save=False) super(ImageModel, self).save(*args, **kwargs) class HistoryError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class BaseHistorizedItem(models.Model): history_modifier = models.ForeignKey(User, related_name='+', verbose_name=_(u"Last editor")) class Meta: abstract = True def save(self, *args, **kwargs): assert hasattr(self, 'history_modifier') == True super(BaseHistorizedItem, self).save(*args, **kwargs) return True def get_previous(self, step=None, date=None, strict=True): """ Get a "step" previous state of the item """ assert step or date historized = self.history.all() item = None if step: assert len(historized) > step item = historized[step] else: for step, item in enumerate(historized): if item.history_date == date: break # ended with no match if item.history_date != date: return item._step = step if len(historized) != (step + 1): item._previous = historized[step + 1].history_date else: item._previous = None if step > 0: item._next = historized[step - 1].history_date else: item._next = None item.history_date = historized[step].history_date model = self.__class__ for k in model._meta.get_all_field_names(): field = model._meta.get_field_by_name(k)[0] if hasattr(field, 'rel') and field.rel: if not hasattr(item, k+'_id'): setattr(item, k, getattr(self, k)) continue val = getattr(item, k+'_id') if not val: setattr(item, k, None) continue try: val = field.rel.to.objects.get(pk=val) setattr(item, k, val) except ObjectDoesNotExist: if strict: raise HistoryError(u"The class %s has no pk %d" % ( unicode(field.rel.to), val)) setattr(item, k, None) item.pk = self.pk return item @property def last_edition_date(self): try: return self.history.order_by('-history_date').all()[0].history_date except IndexError: return def rollback(self, date): """ Rollback to a previous state """ to_del, new_item = [], None for item in self.history.all(): if item.history_date == date: new_item = item break to_del.append(item) if not new_item: raise HistoryError(u"The date to rollback to doesn't exist.") try: for f in self._meta.fields: k = f.name if k != 'id' and hasattr(self, k): if not hasattr(new_item, k): k = k + "_id" setattr(self, k, getattr(new_item, k)) try: self.history_modifier = User.objects.get( pk=new_item.history_modifier_id) except User.ObjectDoesNotExist: pass self.save() except: raise HistoryError(u"The rollback has failed.") # clean the obsolete history for historized_item in to_del: historized_item.delete() def values(self): values = {} for f in self._meta.fields: k = f.name if k != 'id': values[k] = getattr(self, k) return values def get_show_url(self): try: return reverse('show-'+self.__class__.__name__.lower(), args=[self.pk, '']) except NoReverseMatch: return @property def associated_filename(self): if [True for attr in ('get_town_label', 'get_department', 'reference', 'short_class_name') if not hasattr(self, attr)]: return '' items = [self.get_department(), self.get_town_label(), self.short_class_name, self.reference] last_edition_date = self.last_edition_date if last_edition_date: items.append(last_edition_date.strftime('%Y%m%d')) else: items.append('00000000') return slugify(u"-".join([unicode(item) for item in items])) class LightHistorizedItem(BaseHistorizedItem): history_date = models.DateTimeField(default=datetime.datetime.now) class Meta: abstract = True def save(self, *args, **kwargs): super(LightHistorizedItem, self).save(*args, **kwargs) return True class Wizard(models.Model): url_name = models.CharField(_(u"URL name"), max_length=128, unique=True) class Meta: verbose_name = _(u"Wizard") ordering = ['url_name'] def __unicode__(self): return unicode(self.url_name) class WizardStep(models.Model): order = models.IntegerField(_(u"Order")) wizard = models.ForeignKey(Wizard, verbose_name=_(u"Wizard")) url_name = models.CharField(_(u"URL name"), max_length=128) name = models.CharField(_(u"Label"), max_length=128) class Meta: verbose_name = _(u"Wizard step") ordering = ['wizard', 'order'] def __unicode__(self): return u"%s » %s" % (unicode(self.wizard), unicode(self.name)) class UserDashboard: def __init__(self): types = IshtarUser.objects.values('person__person_types', 'person__person_types__label') self.types = types.annotate(number=Count('pk'))\ .order_by('person__person_types') class Dashboard: def __init__(self, model): self.model = model self.total_number = model.get_total_number() history_model = self.model.history.model # last edited - created self.recents, self.lasts = [], [] for last_lst, modif_type in ((self.lasts, '+'), (self.recents, '~')): last_ids = history_model.objects.values('id')\ .annotate(hd=Max('history_date')) last_ids = last_ids.filter(history_type=modif_type) from archaeological_finds.models import Find if self.model == Find: last_ids = last_ids.filter(downstream_treatment_id__isnull=True) if modif_type == '+': last_ids = last_ids.filter(upstream_treatment_id__isnull=True) last_ids = last_ids.order_by('-hd').distinct().all()[:5] for idx in last_ids: try: obj = self.model.objects.get(pk=idx['id']) except: # deleted object are always referenced in history continue obj.history_date = idx['hd'] last_lst.append(obj) # years self.years = model.get_years() self.years.sort() if not self.total_number or not self.years: return self.values = [('year', _(u"Year"), reversed(self.years))] # numbers self.numbers = [model.get_by_year(year).count() for year in self.years] self.values += [('number', _(u"Number"), reversed(self.numbers))] # calculate self.average = self.get_average() self.variance = self.get_variance() self.standard_deviation = self.get_standard_deviation() self.median = self.get_median() self.mode = self.get_mode() # by operation if not hasattr(model, 'get_by_operation'): return operations = model.get_operations() operation_numbers = [model.get_by_operation(op).count() for op in operations] # calculate self.operation_average = self.get_average(operation_numbers) self.operation_variance = self.get_variance(operation_numbers) self.operation_standard_deviation = self.get_standard_deviation( operation_numbers) self.operation_median = self.get_median(operation_numbers) operation_mode_pk = self.get_mode(dict(zip(operations, operation_numbers))) if operation_mode_pk: from archaeological_operations.models import Operation self.operation_mode = unicode(Operation.objects.get( pk=operation_mode_pk)) def get_average(self, vals=[]): if not vals: vals = self.numbers return sum(vals)/len(vals) def get_variance(self, vals=[]): if not vals: vals = self.numbers avrg = self.get_average(vals) return self.get_average([(x-avrg)**2 for x in vals]) def get_standard_deviation(self, vals=[]): if not vals: vals = self.numbers return round(self.get_variance(vals)**0.5, 3) def get_median(self, vals=[]): if not vals: vals = self.numbers len_vals = len(vals) vals.sort() if (len_vals % 2) == 1: return vals[len_vals/2] else: return (vals[len_vals/2-1] + vals[len_vals/2])/2.0 def get_mode(self, vals={}): if not vals: vals = dict(zip(self.years, self.numbers)) mx = max(vals.values()) for v in vals: if vals[v] == mx: return v class DocumentTemplate(models.Model): CLASSNAMES = (('archaeological_operations.models.AdministrativeAct', _(u"Administrative Act")),) name = models.CharField(_(u"Name"), max_length=100) template = models.FileField(_(u"Template"), upload_to="upload/templates/") associated_object_name = models.CharField(_(u"Associated object"), max_length=100, choices=CLASSNAMES) available = models.BooleanField(_(u"Available"), default=True) class Meta: verbose_name = _(u"Document template") verbose_name_plural = _(u"Document templates") ordering = ['associated_object_name'] def __unicode__(self): return self.name @classmethod def get_tuples(cls, dct={}, empty_first=True): dct['available'] = True if empty_first: yield ('', '--') items = cls.objects.filter(**dct) for item in items.order_by(*cls._meta.ordering).all(): yield (item.pk, _(unicode(item))) def publish(self, c_object): tempdir = tempfile.mkdtemp("-ishtardocs") output_name = tempdir + os.path.sep + \ slugify(self.name.replace(' ', '_').lower()) + u'-' +\ datetime.date.today().strftime('%Y-%m-%d') +\ u"." + self.template.name.split('.')[-1] missing = ooo_replace(self.template, output_name, c_object.get_values()) return output_name class Department(models.Model): label = models.CharField(_(u"Label"), max_length=30) number = models.CharField(_(u"Number"), unique=True, max_length=3) class Meta: verbose_name = _(u"Department") verbose_name_plural = _(u"Departments") ordering = ['number'] def __unicode__(self): return unicode(self.number) + settings.JOINT + self.label class Address(BaseHistorizedItem): address = models.TextField(_(u"Address"), null=True, blank=True) address_complement = models.TextField(_(u"Address complement"), null=True, blank=True) postal_code = models.CharField(_(u"Postal code"), max_length=10, null=True, blank=True) town = models.CharField(_(u"Town"), max_length=30, null=True, blank=True) country = models.CharField(_(u"Country"), max_length=30, null=True, blank=True) phone = models.CharField(_(u"Phone"), max_length=18, null=True, blank=True) mobile_phone = models.CharField(_(u"Mobile phone"), max_length=18, null=True, blank=True) history = HistoricalRecords() class Meta: abstract = True class OrganizationType(GeneralType): class Meta: verbose_name = _(u"Organization type") verbose_name_plural = _(u"Organization types") ordering = ('label',) class Organization(Address, OwnPerms, ValueGetter): name = models.CharField(_(u"Name"), max_length=100) organization_type = models.ForeignKey(OrganizationType, verbose_name=_(u"Type")) history = HistoricalRecords() class Meta: verbose_name = _(u"Organization") verbose_name_plural = _(u"Organizations") permissions = ( ("view_organization", ugettext(u"Can view all Organization")), ("view_own_organization", ugettext(u"Can view own Organization")), ("add_own_organization", ugettext(u"Can add own Organization")), ("change_own_organization", ugettext(u"Can change own Organization")), ("delete_own_organization", ugettext(u"Can delete own Organization")), ) def __unicode__(self): return self.name class PersonType(GeneralType): #rights = models.ManyToManyField(WizardStep, verbose_name=_(u"Rights")) groups = models.ManyToManyField(Group, verbose_name=_(u"Groups"), blank=True, null=True) class Meta: verbose_name = _(u"Person type") verbose_name_plural = _(u"Person types") ordering = ('label',) class Person(Address, OwnPerms, ValueGetter) : _prefix = 'person_' TYPE = (('Mr', _(u'Mr')), ('Ms', _(u'Miss')), ('Md', _(u'Mrs')), ('Dr', _(u'Doctor')), ) TABLE_COLS = ('name', 'surname', 'email', 'person_types_list', 'attached_to') title = models.CharField(_(u"Title"), max_length=2, choices=TYPE) surname = models.CharField(_(u"Surname"), max_length=20, blank=True, null=True) name = models.CharField(_(u"Name"), max_length=30) email = models.CharField(_(u"Email"), max_length=40, blank=True, null=True) person_types = models.ManyToManyField(PersonType, verbose_name=_(u"Types")) attached_to = models.ForeignKey('Organization', related_name='members', verbose_name=_(u"Is attached to"), blank=True, null=True) class Meta: verbose_name = _(u"Person") verbose_name_plural = _(u"Persons") permissions = ( ("view_person", ugettext(u"Can view all Person")), ("view_own_person", ugettext(u"Can view own Person")), ("add_own_person", ugettext(u"Can add own Person")), ("change_own_person", ugettext(u"Can change own Person")), ("delete_own_person", ugettext(u"Can delete own Person")), ) def __unicode__(self): values = [unicode(getattr(self, attr)) for attr in ('surname', 'name', 'attached_to') if getattr(self, attr)] return u" ".join(values) def get_values(self, prefix=''): values = super(Person, self).get_values(prefix=prefix) values[prefix+'title'] = dict(self.TYPE)[self.title] if not self.attached_to: values.update(Person.get_empty_values(prefix=prefix + 'attached_to_')) return values person_types_list_lbl = _(u"Types") @property def person_types_list(self): return u", ".join([unicode(pt) for pt in self.person_types.all()]) def has_right(self, right_name): if '.' in right_name: right_name = right_name.split('.')[-1] if type(right_name) in (list, tuple): return bool(self.person_types.filter( txt_idx__in=right_name).count()) or \ bool(self.person_types.filter( groups__permissions__codename__in=right_name).count()) # or self.person_types.filter(wizard__url_name__in=right_name).count()) return bool(self.person_types.filter(txt_idx=right_name).count()) or \ bool(self.person_types.filter( groups__permissions__codename=right_name).count()) # or self.person_types.filter(wizard__url_name=right_name).count()) def full_label(self): values = [] if self.title: values = [unicode(_(self.title))] values += [unicode(getattr(self, attr)) for attr in ('surname', 'name', 'attached_to') if getattr(self, attr)] return u" ".join(values) @property def associated_filename(self): values = [unicode(getattr(self, attr)) for attr in ('surname', 'name', 'attached_to') if getattr(self, attr)] return slugify(u"-".join(values)) class IshtarUser(User): person = models.ForeignKey(Person, verbose_name=_(u"Person"), unique=True, related_name='ishtaruser') class Meta: verbose_name = _(u"Ishtar user") verbose_name_plural = _(u"Ishtar users") @classmethod def create_from_user(cls, user): default = user.username surname = user.first_name or default name = user.last_name or default email = user.email person_type = None if user.is_superuser: person_type = PersonType.objects.get(txt_idx='administrator') else: person_type = PersonType.objects.get(txt_idx='public_access') person = Person.objects.create(title='Mr', surname=surname, name=name, email=email, history_modifier=user) person.person_types.add(person_type) return IshtarUser.objects.create(user_ptr=user, username=default, person=person) def has_right(self, right_name): return self.person.has_right(right_name) class AuthorType(GeneralType): class Meta: verbose_name = _(u"Author type") verbose_name_plural = _(u"Author types") class Author(models.Model): person = models.ForeignKey(Person, verbose_name=_(u"Person"), related_name='author') author_type = models.ForeignKey(AuthorType, verbose_name=_(u"Author type")) class Meta: verbose_name = _(u"Author") verbose_name_plural = _(u"Authors") def __unicode__(self): return unicode(self.person) + settings.JOINT + unicode(self.author_type) def related_sources(self): return list(self.treatmentsource_related.all()) + \ list(self.operationsource_related.all()) + \ list(self.findsource_related.all()) + \ list(self.contextrecordsource_related.all()) class SourceType(GeneralType): class Meta: verbose_name = _(u"Source type") verbose_name_plural = _(u"Source types") class Source(models.Model): title = models.CharField(_(u"Title"), max_length=300) source_type = models.ForeignKey(SourceType, verbose_name=_(u"Type")) authors = models.ManyToManyField(Author, verbose_name=_(u"Authors"), related_name="%(class)s_related") associated_url = models.URLField(verify_exists=False, blank=True, null=True, verbose_name=_(u"Numerical ressource (web address)")) receipt_date = models.DateField(blank=True, null=True, verbose_name=_(u"Receipt date")) creation_date = models.DateField(blank=True, null=True, verbose_name=_(u"Creation date")) reference = models.CharField(_(u"Ref."), max_length=25, null=True, blank=True) internal_reference = models.CharField(_(u"Internal reference"), max_length=25, null=True, blank=True) TABLE_COLS = ['title', 'source_type', 'authors',] class Meta: abstract = True def __unicode__(self): return self.title if settings.COUNTRY == 'fr': class Arrondissement(models.Model): name = models.CharField(u"Nom", max_length=30) department = models.ForeignKey(Department, verbose_name=u"Département") def __unicode__(self): return settings.JOINT.join((self.name, unicode(self.department))) class Canton(models.Model): name = models.CharField(u"Nom", max_length=30) arrondissement = models.ForeignKey(Arrondissement, verbose_name=u"Arrondissement") def __unicode__(self): return settings.JOINT.join((self.name, unicode(self.arrondissement))) class Town(models.Model): name = models.CharField(_(u"Name"), max_length=100) surface = models.IntegerField(_(u"Surface (m²)"), blank=True, null=True) center = models.PointField(_(u"Localisation"), srid=settings.SRID, blank=True, null=True) if settings.COUNTRY == 'fr': numero_insee = models.CharField(u"Numéro INSEE", max_length=6, unique=True) departement = models.ForeignKey(Department, verbose_name=u"Département", null=True, blank=True) canton = models.ForeignKey(Canton, verbose_name=u"Canton", null=True, blank=True) objects = models.GeoManager() class Meta: verbose_name = _(u"Town") verbose_name_plural = _(u"Towns") if settings.COUNTRY == 'fr': ordering = ['numero_insee'] def __unicode__(self): if settings.COUNTRY == "fr": return u"%s (%s)" % (self.name, self.numero_insee[:2]) return self.name