Django ORM: Join two tables using a linked table - django

I want to integrate a function has_permission(self, permission_name) in the class Profile.
For this I want to do make a ORM statement which returns a list of permission names.
The data model is:
1 User has 1 Profile (extends Django's User model)
Each Profile is assigned to one ProfileGroup.
However, different Profiles can belong to the same ProfileGroup.
Each ProfileGroup can have one or more ProfilePermissions. Groups can have the same subsets of ProfilePermissions. The Link-Table GroupPermissions handles this using two ForeignKeys instead of a ManyToMany relation.
My models.py looks like this:
class ProfileGroup(models.Model):
name = models.CharField(max_length=64)
class ProfilePermission(models.Model):
name = models.CharField(max_length=64)
class GroupPermissions(models.Model):
group = models.ForeignKey(ProfileGroup, on_delete=models.RESTRICT)
permission = models.ForeignKey(ProfilePermission, on_delete=models.RESTRICT)
class Meta:
constraints = [
models.UniqueConstraint(fields=['group', 'permission'], name='Unique')
]
def __str__(self):
return f'Group "{self.group.name}" has permission "{self.permission.name}"'
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(ProfileGroup, on_delete=models.RESTRICT)
def __str__(self):
return f'Profile of {self.user.username}'
def has_permission(self, permission_name):
return True
The function has_permission (in the Profile class) should check if the permission permission is included in the list of Permissions. I tried it with
list_of_permissions = GroupPermissions.objects.filter(group=self.group)
But this returns a list of ProfilePermission. What I want is a list of names of the permission (permission.name).
So I have to join the two Tables GroupPermissions and ProfilePermission. I found things as prefetch_related and select_related but this does not work as I want.

Answering your question directly,
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(ProfileGroup, on_delete=models.RESTRICT)
def __str__(self):
return f'Profile of {self.user.username}'
def has_permission(self, permission_name):
return GroupPermissions.objects.filter(group=self.group, permission__name=permission_name).exists()
But... My advice to you is to not try to reinvent the wheel. django.contib.auth has already groups and permissions, just use them ;-)

Related

Django ManyToMany relationship, filter options of modelmultiplechoicefield

I am currently rewriting a web app that is utilizing a hierarchy of relationships which is making it extremely painful to create a reliable work flow for the creation of objects. The current set up is as follows:
Customer Hierarchy:
Organization -> Location -> Room
Contacts can belong to one or many of these entries at any level of the hierarchy. Ex Jim can be assigned to Organization and location.
With this I need to filter the django many to many field that is populated with any contact that doesn't belong anywhere OR belongs to a parent or child level of the customer hierarchy.
I have attempted inline formsets which fails on the many to many model as well as limit_choices_to={'Organization':_get_self_pk} . This works but doesn't allow for the use of django admin style on the fly creation of contacts. I have also attempted to use a queryset in the init function for create, but my form has a nested inline formset that doesn't allow me to use the self.field['contact'] to inject the queryset. (Key Error, contacts doesn't exist as a field)
Models.py
class Organization(AbstractExclusions, AbstractManyToManyCommonInfo, AbstractCommonInfo, AbstractOrganizationLocationCommonInfo, AbstractAcvitivyInfo):
....
contact = models.ManyToManyField('Contact', blank=True)
class Location(AbstractManyToManyCommonInfo, AbstractCommonInfo, AbstractOrganizationLocationCommonInfo, AbstractLocationRoomCommonInfo, AbstractAcvitivyInfo, AbstractExclusions):
....
contact = models.ManyToManyField('Contact', blank=True)
class Room(AbstractManyToManyCommonInfo, AbstractCommonInfo, AbstractLocationRoomCommonInfo, AbstractAcvitivyInfo, AbstractExclusions):
....
contact = models.ManyToManyField('Contact', blank=True)
class Contact(AbstractUserInfo):
phone_number = PhoneNumberField(verbose_name=_('Phone Number'))
is_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.CASCADE, verbose_name=_('Link To Authenticated User'),)
role = models.ForeignKey(refModels.Role, verbose_name=_("Role"), on_delete=models.CASCADE, null=True, blank=True)
This question was answered by creating a special init function as shown below:
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
try:
self.fields['contact'].queryset = (custModels.Contact.objects.filter(Q(organization = None) | Q(organization = self.instance.id)))
except:
pass

Django) How to connect urls-views-models in ManyToMany, OneToMany relationship

I made some models which have ManyToMany, OneToMany relationships, and then I tried to make appropriate class in views.py, so that one can see sub models related to the chosen model.
But in terms of connecting models-serializers-views-urls, I just couldn't figure out how to make it work...
So, what I want to do is : (simplified)
There are 3 models.
Party
People
Food
So Party has ManyToMany relationship with People, and OneToMany relationship with Food. When I reached url like /party_id/people_id, then I want to get specific person's information from given party id.
Here goes my code.
models.py
class Party(models.Model):
par_id = models.TextField()
par_people = models.ManyToManyField(People)
class People(models.Model):
peo_id = models.TextField()
peo_name = models.TextField()
peo_type = models.TextField()
class Food(models.Model):
foo_id = models.TextField()
foo_party = models.ForeignKey(Party, on_delete=models.CASCADE)
serializers.py
class PartySerializer(serializers.ModelSerializer):
class Meta:
model = Party
fields = ('par_id', 'par_people')
# People, Food has same structure...
views.py
class PartyList(generics.ListAPIView):
queryset = Party.objects.all()
serializer_class = PartySerializer
# People, Food has same structure...
urls.py
Here's the part where I got lost
#redundancy reduced...(e.g. import)
urlpatterns = [
path('party/<int:par_id>/<int:peo_id>', views.PartyList.as_view()),
path('party/<int:par_id>/<int:foo_id>', views.PartyList.as_view()),
]
So If I reach website/party/1/3, I want to see person's information(whose peo_id is 3) of party(whose par_id is 1). For food, It goes the same.
Should I make new class in views.py to make it work? But how can url check par_id and foo_id at the same time if I use PartyList view class..? Any help would be much appreciated.
I think something like this should work. The basic principle if work out if using peo_id or foo_id and then filter the queryset on that basis.
def get (self, *args, **kwargs):
id = kwargs.get(peo_id, None)
if id:
self.queryset.filter(par_people__peo_id=id)
else:
id = kwargs.get(foo_id, None)
self.queryset.filter(foo_party=id)

Django's prefetch_related and select_related on more complex relationships in Admin

I have a somewhat complex relationship between multiple models. A simplified example:
class Country(models.Model):
name = models.CharField([...])
[...]
def __ str__(self):
return f'{self.name}'
class Region(models.Model):
country = models.ForeignKey(Country)
name = models.CharField([...])
[...]
def __ str__(self):
return f'{self.name}'
class CityManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('region', 'region__country')
class City(models.Model):
name = models.CharField([...])
region = models.ForeignKey(Region)
objects = CityManager()
def __str__(self):
return f'{self.region.country} - {self.region} - {self.name}'
Hence when I want to display some kind of list of cities (e.g. list all cities in Germany), I have to use select_related to be even remotely efficient otherwise I query for Country each time the __str__ is called. This is not the problem.
The problem is that when I have unrelated group of models and I want to FK to City, such as:
class Tour(models.Model):
[...]
class TourItem(models.Model):
tour = models.ForeignKey(Tour)
city = models.ForeignKey(City)
[...]
Tour would represent a planned tour for some music band; and TourItem would be a specific tour in a given city. I have a simple admin interface for this, so that TourItem is an inline field for the Tour (ie. so multiple tour items can be edited/added simultaneously). The problem is that now there are multiple queries firing for same Country when looking up the City FK and I'm not sure how to solve it. I tried what follows, but it did not work as expected:
class TourManager(models.Manager):
def get_queryset(self):
return super().get_queryset().prefetch_related('touritem_set__city', 'touritem_set__city__region', 'touritem_set__city__region__country')
And neither did this work:
class TourItemManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('city', 'city__region', 'city__region__country')
How can I adjust the managers/models so that when I load Tour's admin there will not be additional queries fired for Country?
You can use ModelAdmin select_related to select related tables
list_select_related = ('author', 'category')
If that is not fully helpful and you still want to do override try with following in your custom Manager
def get_queryset(self, request):
return super(TourItemManager,self).queryset(request).select_related('city', 'city__region', 'city__region__country')

Django Query Does not exist

I'm been trying to create an app that allows users to follow each other profile since yesterday and today and I haven't been successful so far.
I'm having trouble creating a following function that allows me to retrieve users from a particular user he follows.
Example . If John follows Diana . I want to able to retrieve the user called Diana and use it with my modules.
I'm really sorry if this doesn't make sense . I'm trying my hardest to explain my situation.
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
image = models.FileField(upload_to="images/",blank=True,null=True)
class Board(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
Most of these solutions gave me no query
This was one of the solutions I tried.
class UserLink(models.Model):
from_user = models.ForeignKey(User , related_name = "following_set")
to_user = models.ForeignKey(User , related_name = "follower_set")
date_added = models.DateTimeField(default = datetime.now)
def __unicode__(self):
return "%s is following %s" % (self.from_user.username,self.to_user.username)
def save(self,**kwargs):
if self.from_user == self.to_user:
raise ValueError("Cannot follow yourself ")
super(UserLink , self).save(**kwargs)
class Meta:
unique_together = (('to_user','from_user'),)
I tried to retrieve the users that a particular user followed and use it against my modules such as Person but it gave me an error No query exist.
def Follow(request,username=""):
if request.method == "POST":
username = request.POST.get('follow',False)
user = User.objects.get(username=username)
UserLink.objects.create(from_user=request.user,to_user=user)
return HttpResponseRedirect(reverse('world:Profile'))
return HttpResponseRedirect(reverse('world:Profile'))
I also tried this following function but it only followed himself and I changed self to User but it didn't allow me to put the person to follow
class UserProfile(models.Model):
user = models.OneToOneField(User)
follows = models.ManyToManyField('self', related_name='followed_by', symmetrical=False)
>>>from pet.models import *
>>>from django.contrib.auth.models import User
>>>user = User.objects.get(username='Peter')
>>>user1 = User.objects.get(username='Sarah')
>>>p = UserProfile.objects.filter(user=user,follows=user1)
>>>Error no field called follows
How can I create a following class that allows retrieve the people that they followed and use it with my modules such as Person?
Can someone help me . Thannk you community!
If I understand correctly, youu are on the right track with the many to many relationship. What you need is to modify your existing Person class to include this information.
Since information about who someone follows or is following is essentially information about that person and so you shouldn't really need to define a new class to implement that functionality.
I would suggest modifying your Person like so.
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
image = models.FileField(upload_to="images/",blank=True,null=True)
following = models.ManyToManyField('self', related_name='followers', symmetrical=False, blank=True, null=True)
What this line does is makes a many to many relationship between the class Person and its self.
Many to many relationships work a little different to other relationships and I suggest you read the Django documentation https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/.
But you should now be able to setup and access the relationship like this.
>>>john = Person.objects.get(name="John")
>>>diana = Person.objects.get(name="Diana")
>>>john.following.add(diana)//setup the many to many relationship
>>>john.save()
>>>john.following.all()
//This should return a queryset of Person objects which john is following.
//eg Diana
>>>diana.followers.all()
//This should return a queryset of Person objects which are following Diana.
//eg. John.
Easy, how awesome is Django!

django admin add data with fixed value in some field

class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.