예제 #1
0
파일: models.py 프로젝트: maloi/iadmin
class LdapMaillist(ldapdb.models.Model):
    """
    Class for representing an LDAP Maillist entry.
    """
    # LDAP meta-data
    base_dn = "ou=Mailinglists,dc=iapp-intern,dc=de"
    object_classes = ['groupOfNames',
                      'extensibleObject',
                     ]

    # groupOfNames
    cn = CharField(db_column='cn', primary_key=True)
    description =  CharField(db_column='description', blank=True)
    member = ListField(db_column='member')

    # extensibleObject
    mail = CharField(db_column='mail')
    owner = ListField(db_column='owner')
    rfc822MailMember = ListField(db_column='rfc822MailMember', blank=True)

    def __str__(self):
        return self.cn

    def __unicode__(self):
        return self.cn
예제 #2
0
class LdapGroup(ldapdb.models.Model):
    """
    Class for representing an LDAP group entry.
    """
    # LDAP meta-data
    base_dn = "ou=group,dc=iapp-intern,dc=de"
    object_classes = [
        'posixGroup',
        'extensibleObject',
    ]

    # posixGroup
    cn = CharField(db_column='cn', primary_key=True)
    gidNumber = IntegerField(db_column='gidNumber', unique=True)
    description = CharField(db_column='description', blank=True)
    memberUid = ListField(db_column='memberUid')

    # extensibleObject
    owner = ListField(db_column='owner')

    def __str__(self):
        return self.cn

    def __unicode__(self):
        return self.cn
예제 #3
0
    def test_list_field_contains(self):
        where = WhereNode()
        where.add((Constraint("memberUid", "memberUid", ListField()), 'contains', 'foouser'), AND)
        self.assertEquals(where_as_ldap(where), ("(memberUid=foouser)", []))

        where = WhereNode()
        where.add((Constraint("memberUid", "memberUid", ListField()), 'contains', '(foouser)'), AND)
        self.assertEquals(where_as_ldap(where), ("(memberUid=\\28foouser\\29)", []))
예제 #4
0
class Group(BaseLdapModel):
    base_dn = force_text('ou=Groups,' + settings.LDAP_BASE_DN)
    object_classes = force_bytestrings(['posixGroup', 'sambaGroupMapping'])
    # posixGroup attributes
    name = CharField(db_column=force_text('cn'), max_length=200, primary_key=True,
                     validators=list(name_validators))
    gid = IntegerField(db_column=force_text('gidNumber'), unique=True)
    members = ListField(db_column=force_text('memberUid'))
    description = CharField(db_column=force_text('description'), max_length=500, blank=True, default='')
    group_type = IntegerField(db_column=force_text('sambaGroupType'), default=None)
    samba_sid = CharField(db_column=force_text('sambaSID'), unique=True, default='')

    def save(self, using=None):
        self.group_type = 2
        self.set_next_free_value('gid', default=10000)
        # noinspection PyStringFormat
        self.samba_sid = '%s-%d' % (get_samba_sid(), self.gid)
        super(Group, self).save(using=using)
        group_of_names = list(GroupOfNames.objects.filter(name=self.name)[0:1])
        if not group_of_names:
            group = GroupOfNames(name=self.name, members=['cn=admin,' + settings.LDAP_BASE_DN])
            group.save()
        else:
            group = group_of_names[0]
        new_members = ['cn=admin,' + settings.LDAP_BASE_DN] + ['uid=%s,%s' % (x, User.base_dn) for x in self.members]
        if new_members != group.members:
            group.members = new_members
            group.save()
예제 #5
0
파일: models.py 프로젝트: mozilla/domesday
class Tag(ldapdb.models.Model):
    """
    A Domesday tag.
    """
    base_dn        = "ou=tags,dc=mozillians,dc=org"
    object_classes = ['groupOfNames']
    
    name        = CharField(db_column='cn', max_length=32768, primary_key=True)
    members     = ListField(db_column='member')
    description = CharField(db_column='description', max_length=1024)
    
    # Will eventually need a blesstag, and a field describing how to become
    # blessed.
    
    def members_as_people(self):
        # XXX want to say: 
        # return [Person.objects.get(dn=dn) for dn in self.members]
        return [Person.objects.get(uid=dn.split(',')[0].split("=")[1]) 
                for dn in self.members]

    def __str__(self):
        return self.name

    def __unicode__(self):
        return self.name
예제 #6
0
class LdapOA(ldapdb.models.Model):

    base_dn = "cn=OA,ou=Roles,dc=xiaoneng,dc=cn"
    object_classes = ['groupOfUniqueNames']

    cn = CharField(db_column='cn', max_length=200, primary_key=True)
    uniqueMember = ListField(db_column='uniqueMember')
예제 #7
0
class GroupOfNames(BaseLdapModel):
    # a copy of Group, but based on different object classes.
    # required by nslcd!
    base_dn = 'ou=CoreGroups,' + settings.LDAP_BASE_DN
    object_classes = force_bytestrings(['groupOfNames'])
    name = CharField(db_column=force_text('cn'), max_length=200, primary_key=True,
                     validators=list(name_validators))
    members = ListField(db_column=force_text('member'))
예제 #8
0
class LDAPPosixUser(LDAPModel):
    """
    Class for representing an LDAP user entry.
    """

    objects = LDAPPosixUserManager()

    # LDAP meta-data
    base_dn = settings.LDAP_SYNC_USER_BASE_DN
    object_classes = ['top', 'posixAccount', 'inetOrgPerson']

    # inetOrgPerson
    first_name = CharField(db_column='givenName', verbose_name="Prime name")
    last_name = CharField("Final name", db_column='sn')
    common_name = CharField(db_column='cn')
    email = ListField(db_column='mail')

    # posixAccount
    uid = IntegerField(db_column='uidNumber', default=1000)
    group = IntegerField(db_column='gidNumber')
    home_directory = CharField(db_column='homeDirectory')
    login_shell = CharField(db_column='loginShell', default='/bin/bash')
    nadine_id = CharField(db_column='uid', primary_key=True)
    password = CharField(db_column='userPassword')

    if hasattr(settings, 'LDAP_SYNC_USER_HOME_DIR_TEMPLATE'):
        HOME_DIR_TEMPLATE = settings.LDAP_SYNC_USER_HOME_DIR_TEMPLATE
    else:
        HOME_DIR_TEMPLATE = "/home/{}"

    def ensure_gid(self):
        """
        If no group is set then perform a get_or_create() for default members
        group.
        """
        if self.group is None:
            members_group, _ = LDAPPosixGroup.objects.get_or_create(
                name=settings.LDAP_SYNC_MEMBERS_GROUP_CN)
            self.group = members_group.gid

    def ensure_home_directory(self):
        """
        If no home_directory is set then LDAP complains, we can auto-populate.
        """
        if not self.home_directory:
            self.home_directory = self.HOME_DIR_TEMPLATE.format(self.uid)

    def save(self, *args, **kwargs):
        self.ensure_gid()
        self.ensure_home_directory()
        return super(LDAPPosixUser, self).save(*args, **kwargs)

    def __str__(self):
        return "{}:{}".format(self.nadine_id, self.common_name)

    def __str__(self):
        return self.full_name
예제 #9
0
파일: models.py 프로젝트: evilpie/domesday
class Tag(ldapdb.models.Model):
    """
    A Domesday tag.
    """
    base_dn = "ou=tags,dc=mozillians,dc=org"
    object_classes = ['groupOfNames']

    name = CharField(db_column='cn', max_length=32768, primary_key=True)
    members = ListField(db_column='member')

    def __str__(self):
        return self.name

    def __unicode__(self):
        return self.name
예제 #10
0
class LdapGroup(ldapdb.models.Model):
    """
    Class for representing an LDAP group entry.
    """
    # LDAP meta-data
    base_dn = "ou=groups,dc=nodomain"
    object_classes = ['posixGroup']

    # posixGroup attributes
    gid = IntegerField(db_column='gidNumber', unique=True)
    name = CharField(db_column='cn', max_length=200, primary_key=True)
    usernames = ListField(db_column='memberUid')

    def __str__(self):
        return self.name

    def __unicode__(self):
        return self.name
예제 #11
0
class LdapGroup(ldapdb.models.Model):
    """ Represents an LDAP posixGroup entry. """

    connection_name = 'ldap'

    # LDAP meta-data
    base_dn = settings.LDAP_GROUP_DN
    object_classes = ['posixGroup']

    # posixGroup attributes
    gid = IntegerField(db_column='gidNumber', unique=True)
    name = CharField(db_column='cn', max_length=200, primary_key=True)
    members = ListField(db_column='memberUid')

    def __str__(self):
        return self.name

    class Meta:
        managed = False
예제 #12
0
class LDAPPosixGroup(LDAPModel):
    """
    Class for representing an LDAP group entry.
    """

    objects = LDAPPosixGroupManager()
    # LDAP meta-data
    base_dn = settings.LDAP_SYNC_GROUP_BASE_DN
    object_classes = ['posixGroup']

    # posixGroup attributes
    gid = IntegerField(db_column='gidNumber', default=500)
    name = CharField(db_column='cn', max_length=200, primary_key=True)
    usernames = ListField(db_column='memberUid')

    def __str__(self):
        return self.name

    def __str__(self):
        return self.name
예제 #13
0
class LdapGroup(ldapdb.models.Model):
    """ Represents LDAP group stored in LDAP directory, configured in settings.py """
    # LDAP meta-data
    base_dn = "ou=Groups,dc=futurice,dc=com"

    # attributes
    gid = IntegerField(db_column='gidNumber', unique=True)
    name = CharField(db_column='cn', max_length=200, primary_key=True)
    members = ListField(db_column='uniqueMember')

    def __unicode__(self):
        return self.name

    @property
    def members_usernames(self):
        usernames = []
        for member in self.members:
            # The members are given in ldap entries - we need to parse the user id from them
            matched = re.match(r'uid=(?P<uid>\w+?),.*', member)
            usernames.append(matched.group('uid'))
        return usernames
예제 #14
0
class GvUserPortal(ldapdb.models.Model):
    class Meta:
        managed = False
    base_dn = "dc=at"
    object_classes = ['gvUserPortal']
    # RDN == Primary Key: cn is list field _only_ using a single value by convention
    cn = CharField(db_column='cn', max_length=250, primary_key=True)

    description = CharField(db_column='description', max_length=1024)
    gvAdminContactMail = ArrayField(CharField(db_column='gvAdminContactMail ', max_length=256),)
    gvAdminContactName = ArrayField(CharField(db_column='gvAdminContactName', max_length=256),)
    gvAdminContactTel = ArrayField(CharField(db_column='gvAdminContactTel', max_length=32),)
    gvDefaultParticipant = CharField(db_column='gvDefaultParticipant', max_length=32)
    gvMaxSecClass = IntegerField(db_column='gvMaxSecClass')
    gvParticipants = ListField(db_column='gvParticipants', )
    gvPortalHotlineMail = ArrayField(CharField(db_column='gvPortalHotlineMail', max_length=250),)
    gvSupportedPvpProfile = ArrayField(CharField(db_column='gvSupportedPvpProfile', max_length=1024),)

    gvScope = CharField(db_column='gvScope', max_length=250)
    gvSource = CharField(db_column='gvSource', max_length=250)
    gvStatus = CharField(db_column='gvStatus', max_length=250)

    def __str__(self):
        return self.cn

    def __repl__(self):
        return self.dn

    def has_add_permission(self, request):
        return False

    # Allow viewing objects but not actually changing them.
    def has_change_permission(self, request, obj=None):
        return (request.method in ['GET', 'HEAD'] and
                super().has_change_permission(request, obj))

    def has_delete_permission(self, request, obj=None):
        return False
예제 #15
0
class LdapUser(ldapdb.models.Model):
    """
    Class for representing an LDAP user entry.
    """
    #   meta-data
    try:
        user_search = get_login_model()

        base_dn = user_search.user_ldapsearch
    except:
        pass
    object_classes = ['inetOrgPerson']

    #    基类已经定义dn
    #    dn = CharField(max_length=200, primary_key=True)

    username = CharField(db_column='cn', primary_key=True)
    #   定义多处主键时会影响到rdn的生成,最后完整dn生成规则是几个primarykey+base_dn组成。
    user_sn = CharField(db_column='sn')
    email = CharField(db_column='mail', blank=True)
    phone = CharField(db_column='telephoneNumber', blank=True)
    password = CharField(db_column='userPassword')
    sshpublickey = CharField(db_column='sshpublickey', blank=True)
    memberof = ListField(db_column="memberOf", blank=True)

    def __str__(self):
        return self.username

    def __unicode__(self):
        return self.username

    def _save_table(self,
                    raw=False,
                    cls=None,
                    force_insert=None,
                    force_update=None,
                    using=None,
                    update_fields=None):
        """
        Saves the current instance.
        """
        # Connection aliasing
        connection = connections[using]

        create = bool(force_insert or not self.dn)

        # Prepare fields
        if update_fields:
            target_fields = [
                self._meta.get_field(name) for name in update_fields
            ]
        else:
            target_fields = [
                field for field in cls._meta.get_fields(include_hidden=True)
                if field.concrete and not field.primary_key
            ]

        def get_field_value(field, instance):
            python_value = getattr(instance, field.attname)
            return field.get_db_prep_save(python_value, connection=connection)

        if create:
            old = None
        else:
            old = cls.objects.using(using).get(pk=self.saved_pk)
        changes = {
            field.db_column: (
                None if old is None else get_field_value(field, old),
                get_field_value(field, self),
            )
            for field in target_fields
        }

        # Actual saving

        old_dn = self.dn
        new_dn = self.build_dn()
        updated = False

        # Insertion
        if create:
            # FIXME(rbarrois): This should be handled through a hidden field.
            hidden_values = [('objectClass', [
                obj_class.encode('utf-8') for obj_class in self.object_classes
            ])]
            new_values = hidden_values + [
                (colname, change[1])
                for colname, change in sorted(changes.items())
                if change[1] is not None
            ]
            new_dn = self.build_dn()
            logger.debug("Creating new LDAP entry %s", new_dn)
            connection.add_s(new_dn, new_values)

        # Update
        else:
            modlist = []
            for colname, change in sorted(changes.items()):
                old_value, new_value = change
                if old_value == new_value:
                    continue
                modlist.append((
                    ldap.MOD_DELETE if new_value is None else ldap.MOD_REPLACE,
                    colname,
                    new_value,
                ))
#          if new_dn != old_dn:
#               logger.debug("renaming ldap entry %s to %s", old_dn, new_dn)
#               connection.rename_s(old_dn, self.build_rdn())

#            logger.debug("Modifying existing LDAP entry %s", new_dn)
#            connection.modify_s(new_dn, modlist)
#            updated = True
# FIXME  重写了这个因为 build_dn 有坑
            logger.debug("Modifying existing LDAP entry %s", old_dn)
            connection.modify_s(old_dn, modlist)
            updated = True

        self.dn = new_dn

        # Finishing
        self.saved_pk = self.pk
        return updated
예제 #16
0
class LDAPUser(ldapdb.models.Model):
    """ Class representing an LDAP user entry """
    # LDAP metadata
    base_dn = settings.AUTH_LDAP_USER_BASE_DN
    object_classes = settings.AUTH_LDAP_USER_OBJECTCLASS
    # top
    object_class = ListField(db_column='objectClass')
    # person
    last_name = CharField(db_column='sn')
    full_name = CharField(db_column='cn')
    description = CharField(db_column='description')
    phone = CharField(db_column='telephoneNumber', blank=True)
    password = ListField(db_column='userPassword')
    # inetOrgPerson
    first_name = CharField(db_column='givenName')
    email = ListField(db_column='mail')
    username = CharField(db_column='uid', primary_key=True)
    # posixAccount
    uid = IntegerField(db_column='uidNumber', unique=True)
    gid = IntegerField(db_column='gidNumber')
    gecos = CharField(db_column='gecos')
    home_directory = CharField(db_column='homeDirectory')
    login_shell = CharField(db_column='loginShell', default='/bin/bash')
    # ldapPublicKey
    ssh_key = ListField(db_column='sshPublicKey')
    # gentooGroup
    ACL = ListField(db_column='gentooACL')
    birthday = DateField(db_column='birthday')
    developer_bug = ListField(db_column='gentooDevBug')
    gentoo_join_date = ListField(db_column='gentooJoin')
    gentoo_retire_date = ListField(db_column='gentooRetire')
    gpg_fingerprint = ListField(db_column='gpgfingerprint')
    gpg_key = ListField(db_column='gpgKey')
    gravatar = CharField(db_column='gravatar')
    im = ListField(db_column='gentooIM')
    latitude = FloatField(db_column='lat')
    location = CharField(db_column='gentooLocation')
    longitude = FloatField(db_column='lon')
    mentor = ListField(db_column='gentooMentor')
    otp_recovery_keys = ListField(db_column='gentooOTPRecoveryKey')
    otp_secret = CharField(db_column='gentooOTPSecret')
    planet_feed = CharField(db_column='gentooPlanetFeed')
    universe_feed = CharField(db_column='gentooUniverseFeed')
    website = ListField(db_column='website')
    # gentooDevGroup
    roles = CharField(db_column='gentooRoles')
    alias = ListField(db_column='gentooAlias')
    spf = ListField(db_column='gentooSPF')
    # additional ACL fields based on gentooACL
    is_user = ACLField(db_column='gentooACL')
    is_developer = ACLField(db_column='gentooACL')
    is_foundation = ACLField(db_column='gentooACL')
    is_staff = ACLField(db_column='gentooACL')
    is_docs = ACLField(db_column='gentooACL')
    is_council = ACLField(db_column='gentooACL')
    is_trustee = ACLField(db_column='gentooACL')
    is_overlays = ACLField(db_column='gentooACL')
    is_planet = ACLField(db_column='gentooACL')
    is_wiki = ACLField(db_column='gentooACL')
    is_forums = ACLField(db_column='gentooACL')
    is_security = ACLField(db_column='gentooACL')
    is_recruiter = ACLField(db_column='gentooACL')
    is_undertaker = ACLField(db_column='gentooACL')
    is_pr = ACLField(db_column='gentooACL')
    is_infra = ACLField(db_column='gentooACL')
    is_retired = ACLField(db_column='gentooACL')

    def __unicode__(self):
        return self.username
예제 #17
0
파일: models.py 프로젝트: mozilla/domesday
class Person(ldapdb.models.Model):
    """
    A Domesday person.
    """
    base_dn        = "ou=people,dc=mozillians,dc=org"
    object_classes = ['inetOrgPerson', 'domesdayPerson']

    uid          = IntegerField(db_column='uid', max_length=256, 
                                primary_key=True)

    # Other fields to consider include:
    # timezone
    # lat/long (with UI to help users specify with an appropriate degree of
    #           vagueness)
    
    cn           = CharField(db_column='cn', max_length=32768)
    name         = CharField(db_column='displayName', max_length=32768)    
    familyName   = CharField(db_column='sn', max_length=32768)
    nickname     = CharField(db_column='domesdayNickName', max_length=32768)
        
    address      = CharField(db_column='postalAddress', max_length=1024)
    locality     = CharField(db_column='l', max_length=32768)
    country      = CharField(db_column='co', max_length=32768)
    
    phone        = CharField(db_column='telephoneNumber', max_length=32768)
    title        = CharField(db_column='title', max_length=32768)
    bio          = CharField(db_column='description', max_length=1024)
    email        = CharField(db_column='mail', max_length=256)
    urls         = ListField(db_column='labeledURI')
    startyear = IntegerField(db_column='domesdayStartYear', max_length=32768)
    tshirtsize   = CharField(db_column='domesdayTShirtSize', max_length=32768)
    
    password     = CharField(db_column='userPassword', max_length=128)
    
    photo        = ImageField(db_column='jpegPhoto')
    
    def get_tags(self):
        tags = Tag.objects.filter(members__contains=self.dn)
        return [t.name for t in tags]
    
    def set_tags(self, tags):
        # Create Tag objects for each tag in the list (new ones 
        # if necessary) and make sure the user is a member of all of them.
        for tag in tags:
            try:
                t = Tag.objects.get(name=tag)
            except Tag.DoesNotExist:
                t = Tag(name=tag)
                
            if not self.dn in t.members:
                t.members.append(self.dn)
                t.save()
        
        # Remove user from tags which were not specified
        current_tags = Tag.objects.filter(members__contains=self.dn)

        for ct in current_tags:
            if not ct.name in tags:
              if len(ct.members) == 1:
                # They are the only person left with this tag
                ct.delete()
              else:
                ct.members.remove(self.dn)
                ct.save()

    tags = property(get_tags, set_tags)

    def get_accounts(self):
        accts = Account.scoped("uid=%s,%s" % (self.uid, Account.base_dn))
        return accts.objects.all().values()

    def set_accounts(self, accounts):
        # Create Account objects for each account in the list (new ones 
        # if necessary).
        MyAccount = Account.scoped("uid=%s,%s" % (self.uid, 
                                                  Account.base_dn))
        current_accts = MyAccount.objects.all()
        
        """
        XXX FilterExceptions...
        
        for a in accounts:
            try:
                ca = MyAccount.objects.get(domain=a['domain'])
                ca.userid = a['userid']
            except MyAccount.DoesNotExist:
                ca = MyAccount(domain=a['domain'], userid=a['userid'])
            ca.save()
        """
        
        # Remove user from accounts which were not specified
        account_domains = [a['domain'] for a in accounts]
            
        for ca in current_accts:
            if not ca.domain in account_domains:
                ca.delete()
        
    accounts = property(get_accounts, set_accounts)
    
    def get_userid(self, domain):
        accts = Account.scoped("uid=%s,%s" % (self.uid, Account.base_dn))
        account = accts.objects.filter(domain=domain)
        if account:
            return account[0].userid
        else:
            return None

    # This returns an object whose layout matches the PortableContacts schema
    # http://portablecontacts.net/draft-spec.html (retrieved 2011-04-19)
    # XXX Need to check doc more carefully and make sure it actually does in 
    # every respect.
    def json_struct(self):
        json_struct = {
            "displayName": self.name,
            "name": {
                "formatted": self.name,
                "familyName": self.familyName,
            },
            "nickname": self.nickname or "",
            "id": self.uid
        }
        
        if self.tags:
            json_struct["tags"] = [tag for tag in self.tags]
      
        if self.email:
            json_struct["emails"] = [{
                "value": self.email,
                "primary": "true"
            }]
        
        if self.urls:
            json_struct["urls"] = [{ 
                "value": u,
                "type": "other"
            } for u in self.urls]
              
        if self.phone:
            json_struct["phoneNumbers"] = [{
                "value": self.phone
            }]
      
        if self.photo:
            json_struct["photos"] = [{
                # XXX not an absolute URL, only site-relative
                "value": url('domesday.views.photo', pk=self.uid),
                "type": "thumbnail"
            }]
      
        if self.address or self.locality or self.country:
            json_struct["addresses"] = [{
                "streetAddress": self.address or "",
                "locality": self.locality or "",
                "country": self.country or "",
                "formatted": self.address + "\n" + self.country
            }]
      
        if self.accounts:
            json_struct["accounts"] = [{ 
                "domain": a['domain'], 
                "userid": a['userid'] 
            } for a in self.accounts]
            
        return json_struct
    
    def __str__(self):
        return self.name

    def __unicode__(self):
        return self.name

    def save(self, using=None):
        # Make sure all required fields are populated (sn and cn)
        if not self.familyName:
            self.familyName = " "
        if not self.cn:
            self.cn = self.familyName
        
        super(Person, self).save(using)
예제 #18
0
class Netgroup(BaseLdapModel):
    base_dn = 'ou=netgroups,' + settings.LDAP_BASE_DN
    object_classes = force_bytestrings(['nisNetgroup', ])
    name = CharField(db_column=force_text('cn'), primary_key=True)
    triple = ListField(db_column=force_text('nisNetgroupTriple'))
    member = ListField(db_column=force_text('memberNisNetgroup'))
예제 #19
0
파일: models.py 프로젝트: evilpie/domesday
class Person(ldapdb.models.Model):
    """
    A Domesday person.
    """
    base_dn = "ou=people,dc=mozillians,dc=org"
    object_classes = ['inetOrgPerson', 'domesdayPerson']

    uid = IntegerField(db_column='uid', max_length=256, primary_key=True)

    cn = CharField(db_column='cn', max_length=32768)
    givenName = CharField(db_column='givenName', max_length=32768)
    familyName = CharField(db_column='sn', max_length=32768)
    name = CharField(db_column='displayName', max_length=32768)
    nickname = CharField(db_column='domesdayNickName', max_length=32768)

    title = CharField(db_column='title', max_length=32768)
    address = CharField(db_column='postalAddress', max_length=1024)
    locality = CharField(db_column='l', max_length=32768)
    bio = CharField(db_column='description', max_length=1024)
    email = CharField(db_column='mail', max_length=256)
    # XXX Do we need a PasswordField type?
    password = CharField(db_column='userPassword', max_length=128)

    photo = ImageField(db_column='jpegPhoto')

    def get_tags(self):
        return Tag.objects.filter(members__contains=self.dn)

    def set_tags(self):
        pass

    tags = property(get_tags, set_tags)

    # Blog and website are stored as labeledURI fields, with the labels 'blog'
    # and 'website'. This means we need some mapping.
    labeledURIs = ListField(db_column='labeledURI')

    def get_uri(self, tag):
        test = re.compile("^(.*) " + tag + "$")
        results = filter(test.search, self.labeledURIs)
        if len(results):
            return test.match(results[0]).group(1)
        else:
            return ""

    def set_uri(self, tag, value):
        # Extract the old version, if present
        test = re.compile("^(.*) " + tag + "$")
        results = filter(lambda u: not test.search(u), self.labeledURIs)
        # Add the new one
        results.append(value + " " + tag)
        self.labeledURIs = results
        return self.get_uri(tag)

    def get_website(self):
        return self.get_uri("website")

    def set_website(self, website):
        return self.set_uri("website", website)

    website = property(get_website, set_website)

    def get_blog(self):
        return self.get_uri("blog")

    def set_blog(self, blog):
        return self.set_uri("blog", blog)

    blog = property(get_blog, set_blog)

    country = CharField(db_column='co', max_length=32768)
    phone = CharField(db_column='telephoneNumber', max_length=32768)
    tshirtsize = CharField(db_column='domesdayTShirtSize', max_length=32768)
    startyear = IntegerField(db_column='domesdayStartYear', max_length=32768)

    # XXX accounts/system IDs

    # This returns an object whose layout matches the PortableContacts schema
    # http://portablecontacts.net/draft-spec.html (retrieved 2011-04-19)
    # XXX Need to check doc more carefully and make sure it actually does in
    # every respect.
    def json_struct(self):
        json_struct = {
            "displayName": self.name,
            "name": {
                "formatted": self.name,
                "familyName": self.familyName,
                "givenName": self.givenName or ""
            },
            "nickname": self.nickname or "",
            "id": self.uid
        }

        if self.tags:
            json_struct["tags"] = [tag.name for tag in self.tags]

        if self.email:
            json_struct["emails"] = [{"value": self.email, "primary": "true"}]

        if self.blog or self.website:
            json_struct["urls"] = []

            if self.blog:
                json_struct["urls"].append({
                    "value": self.blog,
                    "type": "blog"
                })

            if self.website:
                json_struct["urls"].append({
                    "value": self.website,
                    "type": "home"
                })

        if self.phone:
            json_struct["phoneNumbers"] = [{"value": self.phone}]

        if self.photo:
            json_struct["photos"] = [{
                # XXX not an absolute URL, only site-relative
                "value":
                url('domesday.views.photo', pk=self.uid),
                "type":
                "thumbnail"
            }]

        if self.address or self.locality or self.country:
            json_struct["addresses"] = [{
                "streetAddress":
                self.address or "",
                "locality":
                self.locality or "",
                "country":
                self.country or "",
                "formatted":
                self.address + "\n" + self.country
            }]

    # "accounts": [
    #  {% for a in self.accounts %}
    #    {% if not forlooself.first %}, {% endif %}
    #    {
    #      "domain": a.domain,
    #      "userid": a.user
    #    }
    #  {% endfor %}
    #],

        return json_struct

    def __str__(self):
        return self.name

    def __unicode__(self):
        return self.name