I have a Project model
class Project(models.Model)
name = models.CharField(max_length=255)
members = models.ManyToManyField(User, related_name="members")
and I am using a classbased Update view to restrict only those users who are members of the project.
class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Project
fields = ["name"]
def test_func(self):
members = self.get_object().members.all()
if members.contains(self.request.user):
return True
return False
After looking at the SQL queries through Djang Debug Toolbar, I see the query is duplicated 2 times.
the 1st instance is from this test_func and the other instance is from django generic views, how can I resolve the duplicated query issue
Just filter the queryset to retrieve only projects where the request.user is a member of that Project, so:
class ProjectUpdateView(LoginRequiredMixin, UpdateView):
model = Project
fields = ['name']
def get_querset(self):
return Project.objects.filter(members=self.request.user)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the Project
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the members relation to projects.
Related
It is sometimes desirable to restrict the choices presented by the ForeignKey field of a ModelForm so that users don't see each others' content. However this can be tricky because models do not have access to request.user.
Consider an app with two models:
class Folder(models.Model):
user = models.ForeignKey(get_user_model(), editable=False, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
class Content(models.Model):
folder = models.ForeignKey(Folder, on_delete=models.CASCADE)
text = models.CharField(max_length=50)
The idea is that users can create folders and content, but may only store content in folders that they themselves created.
i.e.:
Content.folder.user == request.user
QUESTION: How can we use for example CreateView, so that when creating new content users are shown the choice of only their own folders?
I do this by overriding CreateView.get_form_class() in order to modify the attributes of the relevant field of the form before it is passed to the rest of the view.
The default method, inherited from views.generic.edit.ModelFormMixin, returns a ModelForm that represents all the editable fields of the model in the base_fields dictionary. So it's a good place to make any desired changes and also has access to self.request.user.
So for the above example, in views.py we might say:
class ContentCreateView(LoginRequiredMixin, CreateView):
model = Content
fields = '__all__'
def get_form_class(self):
modelform = super().get_form_class()
modelform.base_fields['folder'].limit_choices_to = {'user': self.request.user}
return modelform
Read more about ForeignKey.limit_choices_to in the docs.
Note that field choices are enforced by form validation so this should be quite robust.
I'd like to create a formView(based on Django build-in CreateView class) to allow creating new user records, which are from two different models.
Both models sounds like one is user model and another is user profile model.
I wish one form with one submit button to approach it, but I found only one form_class could be assigned in one createView class in Django.
So I wonder is that possible to use CreateView to approach it? if not, any solution recommended? Appreciated if any assistance from you.
So you have two models for user and user_profile. One user one profile
so:
Try this:
#models.py
class User_profile(models.Model):
User= models.Foreignkey('User')
#add more user data
#forms.py
class UserProfileForm(ModelForm):
model = User_profile
fields = ['User']
#views.py
class SomeView(CreateView):
form_class = UserProfileForm()
Actually, I find the solution: use the User model and add profile's field in the same form as below
class userCreateForm:
email = forms.EmailField(label=_("E-mail"))
contact_number = forms.CharField(label=_("Contact"))
contact_address = forms.CharField(label=_("Address"))
class Meta:
model = User
fields = (UsernameField(), "email", "contact_number", "contact_address")
Initially, I started my UserProfile like this:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
verified = models.BooleanField()
mobile = models.CharField(max_length=32)
def __unicode__(self):
return self.user.email
Which works nicely along with AUTH_PROFILE_MODULE = 'accounts.UserProfile' set in settings.py.
However, I have two different kinds of users in my website, Individuals and Corporate, each having their own unique attributes. For instance, I would want my Individual users to have a single user only, hence having user = models.OneToOneField(User), and for Corporate I would want them to have multiple users related to the same profile, so I would have user = models.ForeignKey(User) instead.
So I thought about segregating the model into two different models, IndivProfile and CorpProfile, both inheriting from UserProfile while moving the model-specific attributes into the relevant sub-models. Seems like a good idea to me and would probably work, however I would not be able to specify AUTH_PROFILE_MODULE this way since I'm having two user profiles that would be different for different users.
I also thought about doing it the other way around, having UserProfile inherit from multiple classes (models), something like this:
class UserProfile(IndivProfile, CorpProfile):
# some field
def __unicode__(self):
return self.user.email
This way I would set AUTH_PROFILE_MODULE = 'accounts.UserProfile' and solve its problem. But that doesn't look like it's going to work, since inheritance in python works from left to right and all the variables in IndivProfile will be dominant.
Sure I can always have one single model with IndivProfile and CorpProfile variables all mixed in together and then I would use the required ones where necessary. But that is just doesn't look clean to me, I would rather have them segregated and use the appropriate model in the appropriate place.
Any suggestions of a clean way of doing this?
You can do this in following way. Have a profile which will contains common fields which are necessary in both profiles. And you have already done this by creating class UserProfile.
class UserProfile(models.Model):
user = models.ForeignKey(User)
# Some common fields here, which are shared among both corporate and individual profiles
class CorporateUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Corporate fields here
class Meta:
db_table = 'corporate_user'
class IndividualUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Individual user fields here
class Meta:
db_table = 'individual_user'
There is no rocket science involved here. Just have a keyword which will distinguish between corporate profile or individual profile. E.g. Consider that the user is signing up. Then have a field on form which will differentiate whether the user is signing up for corporate or not. And Use that keyword(request parameter) to save the user in respective model.
Then later on when ever you want to check that the profile of user is corporate or individual you can check it by writing a small function.
def is_corporate_profile(profile):
try:
profile.corporate_user
return True
except CorporateUser.DoesNotExist:
return False
# If there is no corporate profile is associated with main profile then it will raise `DoesNotExist` exception and it means its individual profile
# You can use this function as a template function also to use in template
{% if profile|is_corporate_profile %}
Hope this will lead you some where. Thanks!
I have done it this way.
PROFILE_TYPES = (
(u'INDV', 'Individual'),
(u'CORP', 'Corporate'),
)
# used just to define the relation between User and Profile
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey('Profile')
type = models.CharField(choices=PROFILE_TYPES, max_length=16)
# common fields reside here
class Profile(models.Model):
verified = models.BooleanField(default=False)
I ended up using an intermediate table to reflect the relation between two abstract models, User which is already defined in Django, and my Profile model. In case of having attributes that are not common, I will create a new model and relate it to Profile.
Could be worth to try using a through field. The idea behind it is to use the UserProfile model as through model for the CorpProfile or IndivProfile models. That way it is being created as soon as a Corp or Indiv Profile is linked to a user:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey(Profile, related_name='special_profile')
class Profile(models.Model):
common_property=something
class CorpProfile(Profile):
user=models.ForeignKey(User, through=UserProfile)
corp_property1=someproperty1
corp_property2=someproperty2
class IndivProfile(Profile):
user=models.ForeignKey(User, through=UserProfile, unique=true)
indiv_property1=something
indiv_property2=something
I think that way it should be possible to set AUTH_PROFILE_MODULE = 'accounts.UserProfile', and every time you create either a CorpProfile or a IndivProfile that is linked to a real user a unique UserProfile model is created. You can then access that with db queries or whatever you want.
I haven't tested this, so no guarantees. It may be a little bit hacky, but on the other side i find the idea quite appealing. :)
I am having trouble getting my model manager to behave correctly when using the Admin interface. Basically, I have two models:
class Employee(models.Model):
objects = models.EmployeeManager()
username = models.CharField(max_length=45, primary_key=True)
. . .
class Eotm(models.Model): #Employee of the Month
date = models.DateField()
employee = models.ForeignKey(Employee)
. . .
And I have an EmployeeManager class that overrides the get() method, something like this:
class EmployeeManager(models.Manager):
use_for_related_fields = True
def get(self, *arguments, **keywords):
try:
return super(EmployeeManager, self).get(*arguments, **keywords)
except self.model.DoesNotExist:
#If there is no Employee matching query, try an LDAP lookup and create
#a model instance for the result, if there is one.
Basically, the idea is to have Employee objects automatically created from the information in Active Directory if they don't already exist in the database. This works well from my application code, but when I tried to create a Django admin page for the Eotm model, things weren't so nice. I replaced the default widget for ForeignKey fields with a TextInput widget so users could type a username (since username is the primary key). In theory, this should call EmployeeManager.get(username='whatever'), which would either return an Employee just like the default manager or create one and return it if one didn't already exist. The problem is, my manager is not being used.
I can't find anything in the Django documentation about using custom Manager classes and the Admin site, aside from the generic manager documentation. I did find a blog entry that talked about specifying a custom manager for ModelAdmin classes, but that doesn't really help because I don't want to change the model represented by a ModelAdmin class, but one to which it is related.
I may not be understanding what you're trying to do here, but you could use a custom Form for your Eotm model:
#admin.py
from forms import EotmAdminForm
class EotmAdmin(models.ModelAdmin):
form = EotmAdminForm
#forms.py
from django import forms
from models import Eotm, Employee
class EotmAdminForm(forms.ModelForm)
class Meta:
model = Eotm
def clean_employee(self):
username = self.cleaned_data['employee']
return Employee.get(username=username)
That, in theory, should work. I haven't tested it.
I have a model similar to the following (simplified):
models.py
class Sample(models.Model):
name=models.CharField(max_length=200)
class Action(models.Model):
samples=models.ManyToManyField(Sample)
title=models.CharField(max_length=200)
description=models.TextField()
Now, if Action.samples would have been a ForeignKey instead of a ManyToManyField, when I display Action as a TabularInline in Sample in the Django Admin, I would get a number of rows, each containing a nice form to edit or add another Action. However; when I display the above as an inline using the following:
class ActionInline(admin.TabularInline):
model=Action.samples.through
I get a select box listing all available actions, and not a nifty form to create a new Action.
My question is really: How do I display the ManyToMany relation as an inline with a form to input information as described?
In principle it should be possible since, from the Sample's point of view, the situation is identical in both cases; Each Sample has a list of Actions regardless if the relation is a ForeignKey or a ManyToManyRelation. Also; Through the Sample admin page, I never want to choose from existing Actions, only create new or edit old ones.
I see your point but think of a case where you might need to use custom through model (table). In that case the admin inline form would include the fields for that intermediate model since thats the model you asked the admin to create the form for.
e.g.
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
The admin should display the form for the Memebership model cause thats the model the editable instance is related to.
In your case the through model contains only the 2 foreign keys (1 for the Action model and 1 for the Sample) ands thats why only the list of actions appear.
You could do what you are asking for if django admin supported nested inlines (there is an open ticket about that).