pythondjangopython-2.7django-nonreldjango-mongodb-engine

EmbeddedModelField in custom user model


I'm using django-mongodb-engine to make a site.

I have a custom user model that looks like this:

class User(AbstractUser):
    trigrama = models.CharField(max_length=3, unique=True)
    external_email = models.EmailField(unique=True)
    phone_number = models.CharField(max_length=20, unique=True)

    AbstractUser._meta.get_field_by_name('email')[0]._unique=True

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = [
        'first_name', 'last_name', 'trigrama',
        'external_email', 'phone_number', 'username'
    ]

It works just fine, but then I tried adding an embedded document to the user model:

    permissions = EmbeddedModelField('Permission')

Permission is a class that looks like this:

class Permission(models.Model):
    computers = ListField(EmbeddedModelField('Computer'))
    projects = ListField(EmbeddedModelField('Project'))
    scripts = ListField(EmbeddedModelField('Script'))

Now when I try to crate a user in the shell using User.objects.create() I get the following error:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Python27\lib\site-packages\django\db\models\manager.py", line 149, in create
    return self.get_query_set().create(**kwargs)
  File "C:\Python27\lib\site-packages\django\db\models\query.py", line 416, in create
    obj.save(force_insert=True, using=self.db)
  File "C:\Python27\lib\site-packages\django\db\models\base.py", line 548, in save
    force_update=force_update, update_fields=update_fields)
  File "C:\Python27\lib\site-packages\django\db\models\base.py", line 668, in save_base
    result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
  File "C:\Python27\lib\site-packages\django\db\models\manager.py", line 215, in _insert
    return insert_query(self.model, objs, fields, **kwargs)
  File "C:\Python27\lib\site-packages\django\db\models\query.py", line 1675, in insert_query
    return query.get_compiler(using=using).execute_sql(return_id)
  File "C:\Python27\lib\site-packages\djangotoolbox\db\basecompiler.py", line 583, in execute_sql
    "field) to None!" % field.name)
IntegrityError: You can't set permissions (a non-nullable field) to None!

I tried setting the default to Permission() using:

permissions = EmbeddedModelField('Permission', default=Permission())

but then I get the following error:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Python27\lib\site-packages\django\db\models\manager.py", line 149, in create
    return self.get_query_set().create(**kwargs)
  File "C:\Python27\lib\site-packages\django\db\models\query.py", line 416, in create
    obj.save(force_insert=True, using=self.db)
  File "C:\Python27\lib\site-packages\django\db\models\base.py", line 548, in save
    force_update=force_update, update_fields=update_fields)
  File "C:\Python27\lib\site-packages\django\db\models\base.py", line 668, in save_base
    result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
  File "C:\Python27\lib\site-packages\django\db\models\manager.py", line 215, in _insert
    return insert_query(self.model, objs, fields, **kwargs)
  File "C:\Python27\lib\site-packages\django\db\models\query.py", line 1675, in insert_query
    return query.get_compiler(using=using).execute_sql(return_id)
  File "C:\Python27\lib\site-packages\djangotoolbox\db\basecompiler.py", line 579, in execute_sql
    connection=self.connection
  File "C:\Python27\lib\site-packages\djangotoolbox\fields.py", line 364, in get_db_prep_save
    (embedded_model, type(embedded_instance)))
TypeError: Expected instance of type <class 'users.models.Permission'>, not <type 'unicode'>.

So how should I go about adding an embedded document to the custom user model?

P.S. If I type Permission() in the shell it instantiates just fine, returning a Permission object and not a unicode.


Solution

  • If you read the docs about the way to set a default value for a field, you'll see that it expects to recieve a value... or a callable object. You can't just create the instance then and there, it doesn't work like that (emphasis mine):

    The default cannot be a mutable object (model instance, list, set, etc.), as a reference to the same instance of that object would be used as the default value in all new model instances.

    When you call it yourself (i.e. Permission()) what you get is an instance, which, in turn, returns a unicode (it's calling the __unicode__ function).

    In other words, just drop the trailing ():

    permissions = EmbeddedModelField('Permission', default=Permission)
    

    Now, in truthfulness, this might be problematic, because it doesn't actually saves that new permission object, and the whole thing can become messy (this being permissions and all, you want to be extra careful). So it might be better to create a function to wrap the operation, and use that instead:

    def get_initial_perms():
        p = Permission()
        p.save()
        return p
    
    # later:
    permissions = EmbeddedModelField('Permission', default=get_initial_perms)
    

    A similar solution would be to override the save() method and give the object a permission before calling the acutal save part.