How to get the current user in ModelFormMixin post() method? - django

How do i get the currently logged in user in the following implementation of the LIstview and ModelFormMixin:
class ListMessages(ListView, ModelFormMixin):
model = Message
template_name = 'accounts/list_messages.html'
context_object_name = 'messages'
form_class = MessageHiddenUserForm
#success_url = reverse_lazy('accounts:list_messages', kwargs={'uname': })
def post(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
if self.form.is_valid():
self.form.user_to = self.kwargs['uname']
self.form.user_from = request.user #this doesn't work
self.object = self.form.save()
return self.get(request, *args, **kwargs)

The problem is not where you get the user from, but where you assign it to. Setting arbitrary attributes on a form doesn't do anything at all; you need to assign it to the result of form.save.
obj = self.form.save(commit=False)
obj.user_to = self.kwargs['uname']
obj.user_from = request.user
obj.save()
Note, you must not call self.get() directly like that; you must always redirect after a successful post.
return redirect('accounts:list_messages', kwargs={...})

For method "post" use request.user.username to get current logged-in user.

Related

TypeError: context must be a dict rather than HttpResponseRedirect: Is it really impossible to redirect to a url without using test_func()?

I want to make it such that if the user has blocked me or if I have blocked the user, I want to redirect them back to the homepage and not allow them to view the detail page. Because this is a class based view, do you have any ways for me to achieve what I want and not affecting what already exists?
I tried to do all sorts of things but didn't work, and none of my friends could solve this. I cannot directly use return redirect(HomeFeed:main) because I have other contexts in the same view which I need it to return in the template.
I also do not want to use UserMixin's test_funct() which involves showing a 403 Forbidden Error because it isn’t user friendly and doesn’t show the user what exactly is happening. That’s why I want to do a redirect followed by django messages to inform them why they can’t view the page
class DetailBlogPostView(BlogPostMixin,DetailView):
template_name = 'HomeFeed/detail_blog.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
blog_post=self.get_object()
blog_post.save()
context['blog_post'] = blog_post
account = Account.objects.all()
context['account'] = account
if blog_post.interest_set.exists():
context["interest_pk"]=blog_post.interest_set.first().pk
if blog_post.author in self.request.user.blocked_users.all():
messages.warning(self.request, 'You cannot view post of ideas authors that you have blocked.', extra_tags='blockedposts')
hi = redirect('HomeFeed:main')
context['hi'] = hi
if blog_post.author in self.request.user.blocked_users.all():
messages.warning(self.request, 'You cannot view post of ideas authors that have blocked you.', extra_tags='blockeduposts')
hi = redirect('HomeFeed:main')
context['bye'] = bye
return context
You can do that in get method:
from django.views.generic import DetailView
from django.shortcuts import redirect
class MyDetailView(DetailView):
...
def get(self, request, *args, **kwargs):
self.object = blog_post = self.get_object()
blog_post.save()
if blog_post.author in request.user.blocked_users.all():
messages.warning(request, 'You cannot view post of ideas authors that you have blocked.', extra_tags='blockedposts')
return redirect('HomeFeed:main')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
...
UPDATE
YOUR VIEW:
class DetailBlogPostView(BlogPostMixin,DetailView):
template_name = 'HomeFeed/detail_blog.html'
def get(self, request, *args, **kwargs):
self.object = blog_post = self.get_object()
blog_post.save()
if blog_post.author in request.user.blocked_users.all():
messages.warning(request, 'You cannot view post of ideas authors that you have blocked.', extra_tags='blockedposts')
return redirect('HomeFeed:main')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['blog_post'] = self.object
context['account'] = Account.objects.all()
if self.object.interest_set.exists():
context["interest_pk"] = self.object.interest_set.first().pk
return context

Handling Redirect with URL that has multiple PK

I'm new to Django and having trouble redirecting after the AddContactEvent form has been filled out. After submitting the form, here is the redirect error:
No URL to redirect to. Either provide a url or define a
get_absolute_url method on the Model.
I am having trouble figuring out how to redirect it since the AddContactEvent url path('contacts/<int:pk1>/addcontactevent)
only has one pk. In the EventDetail url there are clearly two pk which would have the contact pk and the event pk. The EventDetail page seems to be creating, but I can't get it to redirect to that page due to multiple PK. how would you handle the redirect?
urls.py
path('contacts/<int:pk>', contact_detail.as_view(), name="contact_detail"),
path('contacts/<int:pk1>/addcontactevent', AddContactEvent.as_view(), name="addcontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>/update', UpdateContactEvent.as_view(), name="updatecontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>', EventDetail.as_view(), name="eventdetail"),
views.py
class AddContactEvent(CreateView):
form_class = ContactEventForm
template_name = 'crm/contactevent.html'
def dispatch(self, request, *args, **kwargs):
"""
Overridden so we can make sure the `Ipsum` instance exists
before going any further.
"""
self.contact = get_object_or_404(Contact, pk=kwargs['pk1'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
""" Save the form instance. """
contact = get_object_or_404(Contact, pk=self.kwargs['pk1'])
form.instance.contact = contact
form.instance.created_by = self.request.user
return super().form_valid(form)
class UpdateContactEvent(UpdateView):
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
class DeleteContactEvent(DeleteView):
model = Event
class EventDetail(DetailView):
template_name = 'crm/eventdetail.html'
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
one way to get rid of the error is to define a get absolute url in contact model
def get_absolute_url(self):
return reverse("contact_detail", kwargs={"pk": self.pk})
You have a saved Event object (which has a pk) and you have the contact pk
def get_success_url(self):
return reverse('eventdetail', kwargs={'pk1': self.kwargs['pk1'], 'pk2': self.object.pk})

Show a paginated ListView and an UpdateView on the same template page

I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().

Clumsy repeat-myself django permission check

I have a class based view for a Problem object. I only want the author of the Problem to be able to view the Problem cbv. Other logged-in users should be redirected to a forbidden page.
I achieve this by checking the ownership in the get_template_name() method. But if I want to pass in context to the forbidden template, I also need to check ownership in the get_context_data() and make the appropriate context.
This works, but it seems like way too much semi-repetitive code, and just very non-Pythonic/Djangonic.
Can any suggest a nicer way to do this? It's an issue for many ClassBasedViews I created. I have both "Problem" objects and "Board" objects that I want to ensure the logged-in user == the author of the Problem or Board object. Almost seems like I could have some kind of Mixin or something.
Anyway, here is an example of my approach:
class ProblemStudyView(UpdateView):
model = Problem
fields = "__all__"
def get_template_names(self):
problem = self.get_object()
if problem.author != self.request.user:
# User should not see this entry
context = {'original_author': problem.author, 'request_user': self.request.user}
template_name = "board/forbidden.html"
return template_name
else:
# This user is OK
template_name = "board/study.html"
return template_name
def get_context_data(self, **kwargs):
context = super(ProblemStudyView, self).get_context_data(**kwargs)
problem = self.get_object()
# Do the permission check a second time to setup correct context
if problem.author != self.request.user:
context = {'original_author': problem.author, 'request_user': self.request.user}
return context
else:
# User is ok; proceed as normal
return context
You could use the PermissionRequiredMixin and a custom Permission.
I'd do something like this:
from django.contrib.auth.mixins import PermissionRequiredMixin
class ProblemStudyView(PermissionRequiredMixin, UpdateView):
model = Problem
fields = "__all__"
permission_denied_message = 'YOURMESSAGEHERE'
raise_exception = True
template_name = 'board/study.html'
def dispatch(self, request, *args, **kwargs):
self.request = request # needed for has_permission
self.kwargs = kwargs # needed for get_object
return super().dispatch(request, *args, **kwargs)
def has_permission(self):
problem = self.get_object()
return problem.author == self.request.user
Since a PermissionDeniedException is raised, you can use (or alter) the standard Django http forbidden view:
https://docs.djangoproject.com/en/1.11/ref/views/#the-403-http-forbidden-view

What is the user's username?

In one of my Django models, I override the save function. I do this to get the user's username. But that keeps failing.
this is what i did:
def save(self, *args, **kwargs):
self.slug = slugify('%s' % (self.question))
if not self.id:
self.publish_date = datetime.datetime.now()
self.publisher = self.request.user
self.modification_date = datetime.datetime.now()
self.modifier = self.request.user
super(Faq, self).save(*args, **kwargs) # Call the "real" save() method
This fails with:
'Faq' object has no attribute 'request'
Thanks.
If this is for use within the Admin app, as you say in your answer to Jake, then you shouldn't override the model save method at all. Instead, you should override the save_model method of the ModelAdmin class.
See the original code in django.contrib.admin.options - you'll see that it's already passed the request object. So all you need to do is to assign the publisher there:
def save_model(self, request, obj, form, change):
obj.slug = slugify('%s' % (obj.question))
if not obj.id:
obj.publish_date = datetime.datetime.now()
obj.publisher = request.user
obj.modification_date = datetime.datetime.now()
obj.modifier = request.user
obj.save()
request is only passed to views, not model methods so you will have to set the publisher in a view.
I would remove the line self.modifier = self.request.user from here and set modifier = request.user at the same time as you set question which I assume you are doing in a view.
Then you can change self.publisher = self.request.user to self.publisher = self.modifier
Hope that helps.
You'll need to pass the request into the save method since it doesn't exist in that context automatically.
def save(self, request, *args, **kwargs):
self.slug = slugify('%s' % (self.question))
if not self.id:
self.publish_date = datetime.datetime.now()
self.publisher = request.user
self.modification_date = datetime.datetime.now()
self.modifier = self.request.user
super(Faq, self).save(*args, **kwargs) # Call the "real" save() method
Usage...
my_faq = Faq()
my_faq.save(request)