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.
Related
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.
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")
I have a model with a foreignkey to another model
class Person(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
class Organisation(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
contact = models.ForeignKey(Person)
I want to use a CreateView to be able to create a new Organisation, but be able to enter a new contact person details on the same page (i.e. when a new organisation is created, a new contact person must also be created).
What is the nicest (DRY) way to do this?
In the CreateView use the model that has the ForeignKey and since it inherits the FormMixin's form_class use the modelform_factory for that model with extra fields the fields of ForeignKey's model. Finally, overload either the validation or save methods with a get_or_create with the ForeignKey's model fields, passing the result to the ModelForm.
An alternative approach would be to chain two CreateViews. First with the Organization as the model, using the Contact's CreateView URL as its success_url. You can even use js to replace the first submit with the html of the second view.
Or you can try some hacks floating around utilizing formsets, though I prefer the first two methods in your case. The formset hacks are better suited for many-to-many relationships.
I'm only using forms.Form but I'm trying to show two choice fields that have selections of the associated models.
It basically needs to show the same names but in both fields. Here's what I'm using.
class ManagersForm(forms.Form):
class Meta:
model = A
leader = forms.ChoiceField()
co-leader = forms.ChoiceField()
Is there not just a way that I can parse the users?
users = MyUser.objects.filter(a=i)
You need to use a ModelForm not Form, if the field from the Model is a ForeignKey the form will render the field as a dropdown of the associated model:
class ManagersForm(forms.ModelForm):
class Meta:
model = A
all. I'm working on the admin for my django site, and I've run into an obstacle.
I've got an Entry model and a Related model. The Related model has two foreign key fields: one to the Entry model (entry) and one to django's User model (author). The Related model is considered a "sub-model" of the Entry model, and each user can only have one Related per Entry.
In the admin, Related is edited inline with Entry. As I have it, the admin shows only one extra Related at a time, and it automatically fills the author field with the current user:
from django.contrib import models
from django.contrib.auth.models import User
class Entry(models.Model):
pass
class Related(models.Model):
entry = models.ForeignKey(Entry)
author = models.ForeignKey(User)
class Meta:
unique_together = ('entry', 'author')
from django.contrib import admin
class RelatedInline(admin.StackedInline):
model = Related
exclude = ('author',)
max_num = 1
class EntryAdmin(admin.ModelAdmin):
inlines = (RelatedInline,)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in filter(lambda obj: isinstance(obj, Related), instances):
if instance.__dict__.get('author', None) is None:
instance.author = request.user
instance.save()
formset.save_m2m()
The problem is that if a user wants to edit an entry which already has a Related by anyone, then only that one related field will be shown.
If possible, I wonder if anyone has any ideas about how I could keep a setup similar to this, but have the admin automatically display the user's related if it exists and an empty form if it doesn't. Barring that, I would just get rid of the line max_num = 1 and replace it with extra = 1. Of course, this would mean that a "new related" form would show even if the user already had one for the current entry, so I wonder if anyone has any idea about how I would catch a possible IntegrityError and let the user know that an error had occurred.
It turns out this is pretty simple. You just need to add a queryset function to your RelatedInline class, specifying which inline to show. If the returned queryset has at least one member, the first will be shown. If the queryset is empty, a single blank inline will be shown!
class RelatedInline(admin.StackedInline):
model = Related
exclude = ('author',)
max_num = 1
def queryset(request):
return Related.objects.filter(author = request.user)