I'm trying to figure out the best way to set up the following django model (genericised for security reasons).
ThingA:
User(M2M through "UserRelation")
ThingB:
User(M2M through "UserRelation")
ThingC:
User(M2M through "UserRelation")
User:
Login_name
UserRelation:
User (foreginkey)
Thing (foreignkey) #is this generic to any of the above "things"
Privilege
I understand using "through" between two distinct models, but I'm not sure how to apply this to multiple models. Would I define a foreignkey for each of the "Thing" models in my UserRelation Model?
It looks like you are trying to setup a generic many-to-many relationship. There is a dedicated django app that you can be use for this purpose: django-gm2m
Here is how to use it in your generic case:
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from gm2m import GM2MField
class ThingA(models.Model):
pass
class ThingB(models.Model):
pass
class ThingC(models.Model):
pass
class User(models.Model):
login_name = models.CharField(max_length=255)
things = GM2MField(through='UserRelation')
class UserRelation(models.Model):
user = models.ForeignKey(User)
thing = GenericForeignKey(ct_field='thing_ct', fk_field='thing_fk')
thing_ct = models.ForeignKey(ContentType)
thing_fk = models.CharField(max_length=255)
privilege = models.CharField(max_length=1)
You can now access all the things for a given user and all the User instances for a given 'thing', as well as the privilege attribute for each UserRelation instance.
This will additionally provide you with a handful of benefits (reverse relations, prefetching, etc.) you may need. A GM2MField basically behaves exactly like a django ManyToManyField.
Disclaimer: I am the author of django-gm2m
Related
I am building a Django web app where I want two things:
Use Django's in-built User model for Django's Admin app usage (store owner)
Use DRF's Token Auth on a custom User model that I will be naming "Customer" (store customer)
How do I keep both the Authentication systems for the above stated purposes. From what I have read every one asks to override the User model but I don't want to do that. Instead I want to keep both. What strategy should I take up?
PS: It might be me, but I am not able to find any solution for this in DRF's Documentation. If there is please do point me in the right direction.
Django does provide an option for using a custom user model. But you can have one and only one user model.
The process is fairly simple, create your own model inheriting django.contrib.auth.models.AbstractUser and specify the AUTH_USER_MODEL settings variable. The Django admin works pretty well with the "custom user model" concept. The DRF token model also uses the settings.AUTH_USER_MODEL var for its OneToOne relation. So, this is can be a viable solution.
To separate out the user types, you can either use a char field with choices representing the user type or use the existing Django groups mechanism. But, in both cases, you could still only have one user model.
For any specific details, you can have OneToOne relations with different models storing extra info.
Something like this would do,
from django.contrib.auth.models import AbstractUser
from model_utils.choices import Choices # Useful package
from django.utils.functional import cached_property
class User(AbstractUser):
USER_TYPES = Choices(
("store_owner", "Store Owner"),
("customer", "Customer"),
)
...hack...
user_type = models.CharField(choices=USER_TYPES)
...hack...
#cached_property
def is_store_owner(self):
return (
self.user_type == self.USER_TYPES.store_owner
and self.store_owner is not None
)
#cached_property
def is_customer(self):
return (
self.user_type == self.USER_TYPES.customer
and self.customer is not None
)
class StoreOwner(models.Model):
user = models.OneToOneField(
"yourapp.User",
related_name="store_owner",
)
# ...extra store owner details...
class Customer(models.Model):
user = models.OneToOneField(
"userapp.User",
related_name="customer",
)
# ...extra customer details...
I'm trying to integrate two django apps where each had their individual auths working. To do that, I'm trying to subclass AbstractUser instead of User. I'm following the PyBB docs and Django#substituting_custom_model. I've removed all migration files in all my apps apart from their individual init.py (including the migrations from the PyBB library sitting in my site-packages). I've also changed the Mysql database to a blank one to start afresh and I'm trying to subclass AbstractUser as shown below.
My Models.py:
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractUser
from django.db import models
class Student_User(models.Model):
"""
Table to store accounts
"""
su_student = models.OneToOneField(AbstractUser)
USERNAME_FIELD = 'su_student'
su_type = models.PositiveSmallIntegerField(db_column='su_type', default=0)
su_access = models.TextField(db_column='su_access', default='')
su_packs = models.TextField(db_column='su_packs', default='')
REQUIRED_FIELDS = []
def __unicode__(self):
return str(self.su_student)
My settings.py:
AUTH_USER_MODEL = "app.Student_User"
PYBB_PROFILE_RELATED_NAME = 'pybb_profile'
When running makemigrations for my primary app, I get this error:
app.Student_User.su_student: (fields.E300) Field defines a relation with model 'AbstractUser', which is either not installed, or is abstract.
How do I achieve what I am trying to do here?
PS: The app was working fine with onetoone with User without username_field or required_field.
PPS: I just checked the AbstractUser model in my contrib.auth.models and it has class Meta: abstract = True. Ok so its abstract, still, how do I resolve this? I just need one login, currently, two parts of my site, although connected through urls, ask for seperate logins and dont detect each others logins. What do I need to do for this?
You can't have a one-to-one relationship with an abstract model; by definition, an abstract model is never actually instantiated.
AbstractUser is supposed to be inherited. Your structure should be:
class Student_User(AbstractUser):
...
I have created my own custom user model in django and specified the same in settings.py :
AUTH_USER_MODEL = 'userprofile.User'
After this, I created User as an abstract class since I wanted to have two separate classes derived from this class namely - Vendor and Customer. This will create two separate database tables.
The problem occurs when I created another class - ProductReview. This class needs a foreign key to the user who added the review and this user can be either a Vendor or a Customer. I am not sure how to put this foreign key constraint here, because settings.AUTH_USER_MODEL will not work, as User is an abstract class.
Following is the class structure:
class UserManager(BaseUserManager):
class User(PermissionsMixin, AbstractBaseUser):
class Meta:
abstract = True
class Vendor(User):
class Customer(User):
class ProductReview(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='reviews', null=True, blank=True)
Edit - I went ahead with three different tables for each of the models.
For your case I would consider changing my modeling. This how I would do it:
Leave the default User as the auth user model for Django.
Create a Vendor and a Customer class with one-to-one relationship to the User model in order to extend it.
In this way, a you can have a ProductReview to reference a User, who could be either a Vendor or a Customer, or you can reference Customers only if you wish.
See more on Django documentation: Extending the User model.
The declaration of user field in ProductReview model class will map with the USER model. To define whether it is a Vendor or a Customer you need to declare another field probably integer field with mapping declared as
TYPE_OF_USER_MAPPING = {
1: 'Vendor',
2: 'Customer'
}
and then create a query to check in which model Vendor or Customer the original USER is.
I want the ability to let users indicate what countries they have visited.. my models.py looks something like this:
class User(models.Model):
name = models.CharField(max_length=50)
countries = models.ManyToManyField(Countries)
class Countries(models.Model):
#This is where I don't know what to do.
#The end goal is for the user to be able to check off what countries he/she has visited
You would create the relationship the other way around
class User(models.Model):
name = models.CharField(max_length=50)
class Countries(models.Model):
user = models.ForeignKey(User)
If you are using django's built in User stuff then you only need this.
from django.contrib.auth.models import User
class Countries(models.Model):
user = models.ForeignKey(User)
Relation fields already generate an attribute on the other model for the reverse relation unless explicitly disabled.
You're fine as is w/r/t the ManyToManyField.
Now you'll want to create a form for this model to allow the checking-off to be done by your users.
In my django site I have two apps, blog and links. blog has a model blogpost, and links has a model link. There should be a one to many relationship between these two things. There are many links per blogpost, but each link has one and only one blog post. The simple answer is to put a ForeignKey to blogpost in the link model.
That's all well and good, however there is a problem. I want to make the links app reusable. I don't want it to depend upon the blog app. I want to be able to use it again in other sites and perhaps associate links with other non-blogpost apps and models.
A generic foreign key seems like it might be the answer, but not really. I don't want links to be able to associate with any model in my site. Just the one that I explicitly specify. And I know from prior experience that there can be issues using generic foreign keys in terms of database usage because you can't do a select_related over a generic foreign key the way you can with a regular foreign key.
What is the "correct" way to model this relationship?
If you think the link app will always point to a single app then one approach would be to pass the name of the foreign model as a string containing the application label instead of a class reference (Django docs explanation).
In other words, instead of:
class Link(models.Model):
blog_post = models.ForeignKey(BlogPost)
do:
from django.conf import setings
class Link(models.Model):
link_model = models.ForeignKey(settings.LINK_MODEL)
and in your settings.py:
LINK_MODEL = 'someproject.somemodel'
I think TokenMacGuy is on the right track. I would look at how django-tagging handles a similar generic relationship using the content type, generic object_id, and generic.py. From models.py
class TaggedItem(models.Model):
"""
Holds the relationship between a tag and the item being tagged.
"""
tag = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
object_id = models.PositiveIntegerField(_('object id'), db_index=True)
object = generic.GenericForeignKey('content_type', 'object_id')
objects = TaggedItemManager()
class Meta:
# Enforce unique tag association per object
unique_together = (('tag', 'content_type', 'object_id'),)
verbose_name = _('tagged item')
verbose_name_plural = _('tagged items')
Anoher way to solve this is how django-mptt does this: define only an abstract model in a reusable app(MPTTModel), and require to inherit it with defining some fields (parent=ForeignKey to self, or whatever your app usecase will require)
Probably you need to use the content types app to link to a model. You might then arrange for your app to check the settings to do some additional checking to limit which content types it will accept or suggest.
I'd go with generic relations. You can do something like select_related, it just require some extra work. But I think it's worth it.
One possible solution for generic select_related-like functionality:
http://bitbucket.org/kmike/django-generic-images/src/tip/generic_utils/managers.py
(look at GenericInjector manager and it's inject_to method)
This question and Van Gale's answer lead me to the question, how it could be possible, to limit contenttypes for GFK without the need of defining it via Q objects in the model, so it could be completly reuseable
the solution is based on
django.db.models.get_model
and the eval built-in, that evaluates a Q-Object from settings.TAGGING_ALLOWED. This is necessary for usage in the admin-interface
My code is quite rough and not fully tested
settings.py
TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb')
models.py:
from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db.models import get_model
from django.conf import settings as s
from django.db import IntegrityError
TAGABLE = [get_model(i.split('.')[0],i.split('.')[1])
for i in s.TAGGING_ALLOWED if type(i) is type('')]
print TAGABLE
TAGABLE_Q = eval( '|'.join(
["Q(name='%s', app_label='%s')"%(
i.split('.')[1],i.split('.')[0]) for i in s.TAGGING_ALLOWED
]
))
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType,
limit_choices_to = TAGABLE_Q)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def save(self, force_insert=False, force_update=False):
if self.content_object and not type(
self.content_object) in TAGABLE:
raise IntegrityError(
'ContentType %s not allowed'%(
type(kwargs['instance'].content_object)))
super(TaggedItem,self).save(force_insert, force_update)
from django.db.models.signals import post_init
def post_init_action(sender, **kwargs):
if kwargs['instance'].content_object and not type(
kwargs['instance'].content_object) in TAGABLE:
raise IntegrityError(
'ContentType %s not allowed'%(
type(kwargs['instance'].content_object)))
post_init.connect(post_init_action, sender= TaggedItem)
Of course the limitations of the contenttype-framework affect this solution
# This will fail
>>> TaggedItem.objects.filter(content_object=a)
# This will also fail
>>> TaggedItem.objects.get(content_object=a)