Django custom auth model - python

Not sure how to name this problem, let me elaborate.
I have very old django project, been migrating it since django 1.5 or so.
It has always had class Member, extending User and has been practically used as authentication model for the app. AUTH_USER_MODEL was never changed, however SOCIAL_AUTH_USER_MODEL is set to custom auth model (and it works that way).
Currently I am migrating to django 3.x and I am ran into trouble with django-registration - it used to work with Member set as model, but now it checks it against AUTH_USER_MODEL. Bottom line is, I want to make it the "right" way and set proper custom model and make sure I do it right before I dig deep into it as I expect it not to be too easy.
When I simply set AUTH_USER_MODEL to custom model, I get error members.Member.user_ptr: (fields.E301) Field defines a relation with the model 'auth.User', which has been swapped out.
When I make Member extend AbstractUser instead of User, I am missing id and fields as django then expects all data in single table (rather than having 2 one-to-one relationship tables).
I could somehow manually merge data into this single table, but I am not even sure if that's the right way.
What would be the best way to keep this project out of trouble for the future? How do I migrate/proceed? I am willing to get my hands dirty :-)

Difficult to help without sample code. Otherwise
Changing AUTH_USER_MODEL after you’ve created database tables is
significantly more difficult since it affects foreign keys and
many-to-many relationships
These articles might help you.
Ruddra.com
Tobias McNulty

Related

Is there any way to add through to M2M field with keeping the related manager's .add() working?

While Django doesn't completely support adding the through attribute to a M2M field (in order to add some extra fields), it can be migrated. The main issue is, Django will complain when any code tries to .add() models to the related set even if there are no required fields in the through model outside of the FKs of the linked models.
So, I want to add a nullable field to the through model and still keep .add() and remove working like it was before (and implicitely using None as the nullable field value). Adding auto_created=True in the meta almost works, but it breaks migrations amongst other things. Is there any way to make it work outside of overriding the many2many descriptor (which isn't exactly included in the public API, though a lot of third-party Django packages use it)?

Custom user model in Django?

I know how to make custom user models, my question is about style and best practices.
What are the consequences of custom user model in Django? Is it really better to use auxiliary one-to-one model?
And for example if I have a UserProfile models which is one-to-one to User, should I create friends relationship (which would be only specific to my app) between UserProfile or between User?
Also all 3rd-party packages rely on get_user_model(), so looks like if I don't use custom user model, all your relations should go to User, right? But I still can't add methods to User, so if User has friends relation, and I want to add recent_friends method, I should add this method to UserProfile. This looks a bit inconsistent for me.
I'd be glad if someone experienced in Django could give a clear insight.
Also all 3rd-party packages rely on get_user_model(), so looks like if I don't use custom user model, all your relations should go to User, right? But I still can't add methods to User, so if User has friends relation, and I want to add recent_friends method, I should add this method to UserProfile.
I have gone down the "one-to-one" route in the past and I ended up not liking the design of my app at all, it seems to me that it forces you away from SOLID. So if I was you I would rather subclass AbstractBaseUser or AbstractUser.
With AbstractBaseUser you are provided just the core implementation of User and then you can extend the model according to your requirements.
Depending on what sort of 3rd-party packages you are using you might need more than just the core implementation: if that's the case just extend AbstractUser which lets you extend the complete implementation of User.
I would definitely recommend using a custom user model - even if you use a one-to-one with a profile. It is incredibly hard to migrate to a custom user model if you've committed to the default user model, and there's almost always a point where you want to add at least some custom logic to the user model.
Whether you use a profile or further extend the user model should then be based on all considerations that usually apply to your database structure. The right™ decision depends on the exact details of your profile, which only you know.

How to add many to one relationship with model from external application in django

My django project uses django-helpdesk app.
This app has Ticket model.
My app got a Client model, which should have one to many relationship with ticket- so I could for example list all tickets concerning specific client.
Normally I would add models.ForeignKey(Client) to Ticket
But it's an external app and I don't want to modify it (future update problems etc.).
I wold have no problem with ManyToMany or OneToOne but don't know how to do it with ManyToOne (many tickets from external app to one Client from my app)
Even more hacky solution: You can do the following in the module level code after you Client class:
class Client(models.Model):
...
client = models.ForeignKey(Client, related_name='tickets')
client.contribute_to_class(Ticket, name='client')
I haven't fully tested it (I didn't do any actual database migrations), but the correct descriptors (ReverseSingleRelatedObjectDescriptor for Ticket and ForeignRelatedObjectsDescriptor for Client) get added to the class, and South recognizes the new fields. So far it seems to work just like a regular ForeignKey.
EDIT: Actually not even that hacky. This is exactly how Django sets up foreign keys across classes. It just reverses the process by adding the field when the reverse related class is built. It won't raise an error if any of the original fields on either model is shadowed. Just make sure you don't do that, as it could potentially break your code. Other than that, I don't think there should be any issues.
There are (at least) two ways to accomplish it:
More elegant solution: Use a TicketProfile class which has a one-to-one relation to Ticket, and put the Client foreign key into it.
Hacky solution: Use a many-to-many relation, and manually edit the automatically created table and make ticket_id unique.

Creating migrations for auth app in django

I need to add a couple fields to Group model in django contrib.auth app using:
field_name = models.CharField(...)
field_name.contribute_to_class(Group, 'field_name')
My issue is while creating the migrations with South, since they are created in the "migrations" directory inside the auth app and, since the system is already in production, I'm not allowed to alter the current django installation in the server in order to migrate auth.
Does anyone know how to create and load such migrations?
Thanks in advance for your help.
Django doesn't make it particularly easy to modify the standard models. I wouldn't recommend that you sublass Group, because it's quite annoying to get built-in functionality to reference the new model instead.
The usual thing to do here is to create a GroupProfile model that has a Group as a unique foreign key. It might not be elegant, but it won't have the huge maintenance overhead that comes with forking Django's source code.
Also: if you can't modify the Django code on the server, you aren't going to be able to do this with raw SQL hackery or a clever migration. South isn't going to be the problem -- the problem is that the Django ORM is going to notice that there are fields present in the SQL table that aren't specified in the code, which will cause it to throw an exception.
Since you use a hack to patch up the model, I think you should write a migration manually. Try copying another migration and changing add_column and models first, if it fails - there is always an option named "raw sql" :)

In Django, how can you change the User class to work with a different db table?

We're running django alongside - and sharing a database with - an existing application. And we want to use an existing "user" table (not Django's own) to store user information.
It looks like it's possible to change the name of the table that Django uses, in the Meta class of the User definition.
But we'd prefer not to change the Django core itself.
So we were thinking that we could sub-class the core auth.User class like this :
class OurUser(User) :
objects = UserManager()
class Meta:
db_table = u'our_user_table'
Here, the aim is not to add any extra fields to the customized User class. But just to use the alternative table.
However, this fails (likely because the ORM is assuming that the our_user_table should have a foreign key referring back to the original User table, which it doesn't).
So, is this sensible way to do what we want to do? Have I missed out on some easier way to map classes onto tables? Or, if not, can this be made to work?
Update :
I think I might be able to make the change I want just by "monkey-patching" the _meta of User in a local_settings.py
User._meta.db_table = 'our_user_table'
Can anyone think of anything bad that could happen if I do this? (Particularly in the context of a fairly typical Django / Pinax application?)
You might find it useful to set up your old table as an alternative authentication source and sidestep all these issues.
Another option is to subclass the user and have the subclass point to your user-model. Override the save function to ensure that everything you need to do to preserve your old functionality is there.
I haven't done either of these myself but hopefully they are useful pointers.
Update
What I mean by alternative authentication in this case is a small python script that says "Yes, this is a valid username / password" - It then creates an instance of model in the standard Django table, copies fields across from the legacy table and returns the new user to the caller.
If you need to keep the two tables in sync, you could decide to have your alternative authentication never create a standard django user and just say "Yes, this is a valid password and username"

Categories