django form exclude a user instance from a queryset - python

I have the following model:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name="user")
people_interested = models.ManyToManyField(User, related_name="interested")
Now I want a form where I want to offer users a form where they can choose people_interested, so I add the following forms.py
class ChooseForm(forms.Form):
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
and then in views:
form = ChooseForm(data = request.POST or None)
if request.POST and form.is_valid():
uprofile, created = UserProfile.objects.get_or_create(user=request.user)
uprofile.people_interested = form.cleaned_data['peers']
uprofile.save()
return HttpResponseRedirect("/")
else:
return render(request, "form_polls.html", {'form':form})
But the trouble with this is, the current user instance also gets displayed. So I tried the following in views.py:
form = ChooseForm(request.user.id, data = request.POST or None)
and then in forms.py
class ChooseForm(forms.Form):
def __init__(self, uid, *args, **kwargs):
super(ChooseForm, self).__init__(*args, **kwargs)
self.fields['peers'].queryset = User.objects.exclude(id=uid)
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
But the above is not a clean implementation, is there a better method of doing it??

What makes you say this is not a clean implementation? Overwriting queryset on __init__ is perfectly acceptable.
The only things I'd do to improve your code is using a post_save signal on User to create it's UserProfile, then just do user.get_profile() on your view. See this question
You could also use a ModelForm for UserProfile instead of a regular form, and limit the fields to people_interested.

Related

how can i post form object, correctly?

i am practicing CBV , so i thought to check if i can override methodes, well one of biggest problems is that idk how to use data(like data just submitted ), i wrote this code for a DetailView so i could see post and comments under it:
class ArtDetailView(FormView, DetailView):
model = Art
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super(ArtDetailView, self).get_context_data(**kwargs)
context['time'] = timezone.now()
context['form'] = self.get_form()
return context
def form_valid(self, form):
form.instance.writer = self.request.user
form.instance.text = self.post
#form.instance.art = Art.objects.get(id=self.pk)
form.save()
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('pages:art_detail', args=(self.kwargs['pk'],))
forms.py:
from django import forms
from .models import Art, Comment
class CommentForm(forms.ModelForm):
class Meta():
model = Comment
fields = ['text','art']
but when i post something it is in this shape:screen_shot(2nd comment)
,i think problem is withform.instance.text = self.post but i don't know how to fix it
can you please also explain a little because all i want is to learn.
and i tried to also add art as autofill(i added as comment) but wasn't successful, can you pls check it it too.
You can overide post method to save form as well. Since you only adding form which is always a text and no possible errors in forms unless blank. You code snippet will look like this
# In forms.py
def CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text', )
# In views.py
class ArtDetailView(FormView, DetailView):
# same as previous but remove form_valid method
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
form_instance = form.save(commit=False)
# this will give form instance and then add related field and save again (commit=True by default)
form_instance.writer = request.user
# since it is art detail view, self.get_object() will give art object
form_instance.art = self.get_object()
form_instance.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super().form_invalid(from)
If you go to FormView declaration you will find like this

How to track which user is creating object for a model and how to show the object details only to that user in django

I am doing an online classroom project in Django where I created a model named create_course which is accessible by teachers. Now I am trying to design this as the teacher who creates a class only he can see this after login another teacher shouldn't see his classes and how to add students into that particular class I created
the course model
class course(models.Model):
course_name = models.CharField(max_length=200)
course_id = models.CharField(max_length=10)
course_sec = models.IntegerField()
classroom_id = models.CharField(max_length=50,unique=True)
created_by = models.ForeignKey(User,on_delete=models.CASCADE)
here if I use "the created_by" field in forms it appears to be a drop-down menu where every user is showing but I want to automatically save the user who creates the object
views.py
def teacher_view(request, *args, **kwargs):
form = add_course(request.POST or None)
context = {}
if form.is_valid():
form.save()
return HttpResponse("Class Created Sucessfully")
context['add_courses'] = form
return render(request, 'teacherview.html', context)
forms.py
from django import forms
from .models import course
class add_course(forms.ModelForm):
class Meta:
model = course
fields = ('course_name', 'course_id', 'course_sec', 'classroom_id')
You can inject the logged in user to the .created_by of the .instance in the form, so:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def teacher_view(request, *args, **kwargs):
if request.method == 'POST':
form = add_course(request.POST, request.FILES)
if form.is_valid():
form.instance.created_by = request.user
form.save()
return redirect('name-of-some-view')
else:
form = add_course()
return render(request, 'teacherview.html', {'add_courses': form})
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: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: Usually a Form or a ModelForm ends with a …Form suffix,
to avoid collisions with the name of the model, and to make it clear that we are
working with a form. Therefore it might be better to use CourseForm instead of
add_course.
Note: Models in Django are written in PascalCase, not snake_case,
so you might want to rename the model from course to Course.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
In your view use commit=False to stop the form from saving until you add the created_by field.
def teacher_view(request, *args, **kwargs):
form = add_course(request.POST or None)
context = {}
if form.is_valid():
course = form.save(commit=False)
course.created_by = request.user
course.save()
return HttpResponse("Class Created Sucessfully")
context['add_courses'] = form
return render(request, 'teacherview.html', context)

How to retrieve current logged in user for dynamic file name use in model

I'm trying to set the current 'upload_to=' directory equal to the current logged-in user's username so that each file uploaded is saved into the user's own directory.
I have tried to follow the django documentation which looks similar to this...
from django.db import models
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class UploadReports(models.Model):
upload = models.FileField(upload_to=user_directory_path, null=True)
I have also tried to add RequestMiddleware to achieve this but it felt wrong as I was implementing it.
I want it to grab the current logged in user and use it in the directory path. The error that comes up is: AttributeError at /stylist/
'UploadReports' object has no attribute 'user'
Solution: The Django documentation does not specify a user needing to be added to the model - though it does expect one.
When it was done the model looked like this:
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'uploads/{0}/{1}'.format(instance.user.username, filename)
class UploadReports(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
xls = models.FileField(upload_to=user_directory_path)
If you add the user here then DO NOT FORGET to add the user to the field of the form as such:
class DocumentForm(forms.ModelForm):
class Meta:
model = UploadReports
fields = ('xls', 'user')
Once you add the field to the form there becomes a new field in the template form with the list of possible users. As most people probably don't, I didn't want the form to include the user. Therefore, as ilja stated, you must exclude the form as such:
class DocumentForm(forms.ModelForm):
class Meta:
model = UploadReports
fields = ('xls', 'user')
exclude = ('user', ) # make sure this is a tuple
Once the form is excluded it will go back to throwing the error that the user does not exist. So you need to add the user in the post method of theviews.py as such:
class FileUploadView(View):
form_class = DocumentForm
success_url = reverse_lazy('home')
template_name = 'file_upload.html'
def get(self, request, *args, **kwargs):
upload_form = self.form_class()
return render(request, self.template_name, {'upload_form': upload_form})
def post(self, request, *args, **kwargs):
upload_form = self.form_class(request.POST, request.FILES)
if upload_form.is_valid():
form_done = upload_form.save(commit=False) # save the form but don't commit
form_done.user = self.request.user # request the user
form_done.save() # finish saving the form
return redirect(self.success_url)
else:
return render(request, self.template_name, {'upload_form': upload_form})
It is not an easy task but it is rewarding when it is done!

Does Django's get_queryset() in admin prevent malicious object saving?

I am developing a multi-tenant app in Django. In the Django admin, some querysets are filtered based on the user, using get_queryset().
Up till now, when a user updated an object from the Django change form, I would validate the data by creating a ModelAdmin form using a factory function to capture the HttpRequest object, then ensure that the Guest object's user was the current user:
EXAMPLE
models.py
class Guest(models.Model):
guest_name = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE)
admin.py
#admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
def get_queryset(self, request)
qs = super().get_queryset(request)
return qs.filter(user=request.user)
def get_form(self, request, obj=None, **kwargs):
self.form = _guest_admin_form_factory(request)
return super().get_form(request, obj, **kwargs)
forms.py
def _guest_admin_form_factory(request):
class GuestAdminForm(forms.ModelForm):
class Meta:
model = Guest
exclude = []
def clean_user(self):
user = self.cleaned_data.get('user', None)
if not user:
return user
if user != request.user:
raise forms.ValidationError('Invalid request.')
return user
return GuestAdminForm
It occurred to me that Django might use the get_queryset() method to validate this for me, since some simple logging showed that the method is called twice when an object gets updated from the change form.
Is this the case, or do I need to stick to validating through a ModelAdmin form?
The documented way to do this is to define has_change_permission():
#admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).filter(user=request.user)
def has_change_permission(self, request, obj=None):
return (obj is None or obj.user == request.user)
No need to muck about with the form.

Django CBV Detailview

Hello Everybody excuse my english....
I am facing a problem with django.
I need to restrict object so only their owners can print it.
Model.py
class Post(models.Model):
title = models.CharField(max_length=50, blank=False)
prenom = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, null=False)
View.py
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
url.py
url(r'detail-post/(?P<pk>[-\d]+)$', views.detailpost.as_view(), name='detailpost'),
This works properly but the problem is that every users can access to the post of another user (http://localhost:8000/detail-post/1). So my question is how can i do some stuff befor rendering the page and see if the post belongs to the current user if yes we print it else we redirect the user to another page.
You can use the LoginRequiredMixin (new in Django 1.9) to make sure that only logged in users can access the view.
Then override the get_queryset method, and filter the queryset so that it only includes posts by the logged-in user.
from django.contrib.auth.mixins import LoginRequiredMixin
class DetailPost(LoginRequiredMixin, DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get_queryset(self):
queryset = super(DetailPost, self).get_queryset()
return queryset.filter(owner=self.request.user)
If the user views a post that does not belong to them, they will see a 404 page. If you must redirect the user instead of showing a 404, then you'll have to take a different approach.
Note that I have renamed your class DetailPost (CamelCase is recommended for classes in Django. You'll have to update your urls.py as well.
You can override get() or post() method in your view class.
from django.shortcuts import redirect
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get(self, request, *args, **kwargs):
self.post = Post.objects.get(pk=self.kwargs['pk'])
if self.post.user != request.user or not request.user.is_superuser:
return redirect('login')
else:
return super(detailpost, self).get(request, *args, **kwargs)
You should override 'get()' method in your 'detailpost' class, so that it would be something like below:
def get(self, request, *args, **kwargs):
queryset = self.model._default_manager.filter(user=request.user)
self.object = self.get_object(queryset)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
It seems like it is the only way to pass User from Request instance to filter queryset.
I did not find that DetailView uses self.request

Categories