User Profiles: Pros, Cons, Pitfalls

    It's no secret that working with user profiles in Django is nothing but a misfortune. We all came across the monolithicity of the model auth.User, the inadequate set of fields in it, as well as all the tricks that had to be resorted to.

    Everyone had to be perverted: not only to users of the dzhanga, but also to its core developers themselves. Remember, for example, how in Django 1.2 it suddenly became possible to use usernamedog symbols (@) and dots in a field ? Do you know why? So that you can use e-mail addresses as logins .

    We, ordinary users, also had a hard time. In order to change a user’s profile by adding any interesting fields to him - a seemingly ordinary thing, right? - had to act in different ways.

    • Take, for example, inheritance . It was necessary to create your own model, inherited from auth.User...

      # models.py
      from django.db import models
      from django.contrib.auth.models import User
      class MyUser(User):
          birthday = models.DateField()    
      

      ... and write a middleware that would replace the class for request.user. Or not middleware, but AUTHENTICATION_BACKEND. It doesn’t matter :) The

      advantage of the scheme was that our “client” code (that is, the project code) didn’t get complicated, we just worked with our user as with a regular dzhangovskiy.

      The main disadvantage of such a scheme is that when inheriting models, not one table is filled in the database, but two: the original dzhangovskaya django_authand ours ourproj_user, in which there is a foreign key on django_auth. Yes, model inheritance in Django is just OneToOneFieldwith some additional attributes. Want to use - keep in mind.

    • No less well-known crutch proposed by the creators of Django - the so-called profile model . We were asked to create a Profileone-to-one model on auth.User...

      # models.py
      from django.db import models
      from django.contrib.auth.models import User
      class Profile(models.Model):
          user = models.OneToOneField(User)
          birthday = models.DateField()
      

      ... and then add something to settings:

      AUTH_PROFILE_MODULE = 'accounts.Profile'

      After that, in the client code, we could work with the profile:

      
      profile = request.user.get_profile()
      profile.birthday = datetime.today()
      profile.save()
      

      Plus, with such a scheme, I don’t know :) maybe any set of any fields with any name? With the minuses, everything is more transparent:
      • complexity of support: now we have not one object for editing, but two. We must not forget that we change the date of birth for profile, and, for example, the password for user. No wonder and get confused.

      • misappropriation of resources: each call to get_profile()causes a query to the database. On pages where there is only one user instance (editing, for example), this is not scary. If such a thing is, for example, in the comments, the result will be disastrous. Of course, select_related()as you know, it will not save, because it does not Userdepend on Profile, but vice versa.
      • and still you have to do everything with your hands! Creating a model Userdoes not mean that a related model will be created automatically Profile. But when referring to a get_profile()newly created user, an exception will fly out - here there is no doubt. And although this trouble is treated in a few lines with a simple signal,

        # profile.models
        from django.db import models
        from django.contrib.auth.models import User
        class Profile(models.Model):
            'что-нибудь хорошее'
        def create_profile(sender, **kwargs):
            if kwargs['created']:
                Profile.objects.create(user=kwargs['instance'])
        models.signals.post_save.connect(create_profile, sender=User)
        

        nevertheless annoying is the need for its “manual” solution.
    • Mankipatching , that is, changing the behavior of a program without rewriting code. After initializing the application in some place of the project (as a rule, in the root urls, settingsor modelsspecially wound up application) they wrote code modifying our user:

      # monkey_patching.models
      from django.db import models
      from django.contrib.auth.models import User
      User.add_to_class('birthday', models.DateField() )
      

      Plus, as with inheritance, ease in client code. Cons - non-obviousness. As you know, magic must be handled very carefully, because you can accidentally redefine some thing, but it will pop up in a completely different place. On the other hand, if very carefully, then why not?

    Speaking of monkeys ...


    The guys who gave the world the legendary sorl.thumbnail once again excelled and made another killer thing on the account. Meet: django-primate , an application that using mankipatching techniques (primates and monkeys, can you feel the correlation?) Makes it very easy to turn your own model into auth.User. That is, in Russian, to compose a profile of the desired fields.

    Getting started is easy enough. First you need to put django-primate. It is possible with PyPI:

    pip install django-primate

    ... and you can also the latest version from their repository:

    pip install  -e git+https://github.com/aino/django-primate.git#egg=django-primate

    You need to call the patch at startup. The creators recommend using it inmanage.py

    #!/usr/bin/env python
    from django.core.management import setup_environ, ManagementUtility
    import imp
    try:
        imp.find_module('settings') # Assumed to be in the same directory.
    except ImportError:
        import sys
        sys.stderr.write(
            "Error: Can't find the file 'settings.py' in the directory "
            "containing %r. It appears you've customized things.\nYou'll have to "
            "run django-admin.py, passing it your settings module.\n" % __file__
            )
        sys.exit(1)
    import settings
    if __name__ == "__main__":
        setup_environ(settings)
        import primate
        primate.patch()
        ManagementUtility().execute()
    

    now all that remains is to point to settingsthe model class that we want to use

    AUTH_USER_MODEL = 'users.models.User'

    ... and you can start to come up with models:

    # users.models
    from django.db import models
    from primate.models import UserBase, UserMeta
    class User(UserBase):
        __metaclass__ = UserMeta
        birthday = models.DateField()
        # На что фантазии хватит?
    


    Now, every time a component of the project addresses it django.contrib.auth.models.User, it will receive a model users.models.User. The converse is also true. The admin panel will be patched automatically, no special steps are required to connect it.

    By default, the django-primate user model has the following differences from auth.User:
    • Fields removed first_nameand last_name, but added name;
    • The maximum field length usernameis 50 characters (at auth.User30)
    • Paul emailadded a unique index
    • The method get_profilereturns self, so you can not worry about code that usesuser.get_profile()

    Otherwise, the primate user’s model walks like a duck, swims like a duck and quacks as a duck behaves very much like the Dzhangovsky source.

    Of course, you can use absolutely any number of fields, but in this case there is a risk that there will be an incompatibility between the third-party applications, so that field username, passwordand emailit is better not to rename.

    Another point: for compatibility with the junga, primate will download the model so that it app_labelbecomes not users(as in the example), but auth. This is especially true for South users who may not understand why for some time.

    ./manage.py schemamigration users --auto

    does not create any migrations.

    However, everyone can get acquainted with README, which I very freely translated. If I missed something, made a mistake or made an inaccuracy, write in the comments or share your thoughts about.

    Thanks for attention :)

    Also popular now: