Django DetailView + show related record of another Model - django

I would like to return to the User related record.
Somebody can help me?
part of my view
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
Something like a old way>
def my_view(request, slug):
var = get_object_or_404(Model, slug=slug)
xxx = AnotherModel.objects.filter(var=var)
...
how can i perfome this in the first view UserProfileDetailView,
show related data?

What I do in this case is add the related object into the context data. It would be something like this:
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_context_data(self, **kwargs):
# xxx will be available in the template as the related objects
context = super(UserProfileDetailView, self).get_context_data(**kwargs)
context['xxx'] = AnotherModel.objects.filter(var=self.get_object())
return context

Another approach is to extend DetailView with MultipleObjectMixin, as in this example:
from django.views.generic import DetailView
from django.views.generic.list import MultipleObjectMixin
from django.contrib.auth import get_user_model
class DetailListView(MultipleObjectMixin, DetailView):
related_model_name = None
def get_queryset(self):
# a bit of safety checks
if not hasattr(self, "related_model_name"):
raise AttributeError(
"%s.related_model_name is missing." % (
self.__class__.__name,))
if not self.related_object_name:
raise NotImplementedError(
"%s.related_model_name must not be None." % (
self.__class__.__name,))
# get the object
obj = self.get_object()
# get the related model attached to the object
related_model = getattr(obj, "%s_set" % self.related_model_name, None)
# safety check if related model doesn't exist
if not related_model:
raise AttributeError(
"%s instance has no attribute \"%s_set\"" % (
obj.__class__.__name__, self.related_model_name)
# return the related model queryset
return related_model.all()
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
return super(DetailListView, self).get(request, *args, **kwargs)
class UserProfileDetailView(DetailListView):
template_name = "perfil.html"
model = get_user_model()
slug_field = "username"
related_model_name = "anothermodel"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
In my opinion this approach is a little less cleaner and understandable, but it has a huge advantage in reusability. It definitely has one downside: if you are using the class variable context_object_name, it will refer to the related objects list and not to the object itself (this has to do with how the inheritance chain is set up when constructing the class).

Related

Unable to restrict access to the post edit screen with django

I am restricting access to the posted edit screen.
I want to make sure that only the user who posted or the super user can edit the post.
For that purpose, "UserPassesTestMixin" is used.
However, there is no limit.
And I think that my own "OnlyRecordEditMixin" is not responding. If you understand, thank you.
#mixin
class OnlyRecordEditMixin(UserPassesTestMixin):
raise_exception = True
def test_func(self):
user = self.request.user
id = self.kwargs['id']
print(id)
return user.pk == URC.objects.get(id=id).user or user.is_superuser
#view
class RecordDetailEdit(UpdateView,OnlyRecordEditMixin):
template_name = 'records/detail_edit.html'
model = URC
pk_url_kwarg = 'id'
form_class = RecordDetailEditForm
success_url = reverse_lazy('person:home')
def get_object(self, queryset=None):
obj = URC.objects.get(id=self.kwargs['id'])
return obj
#url
path('<id>/edit/', views.RecordDetailEdit.as_view(), name='record_detail_edit'),
#model
class URC(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
A mixin should be put before the base view class, to construct a good MRO here.
class RecordDetailEdit(OnlyRecordEditMixin, UpdateView):
template_name = 'records/detail_edit.html'
model = URC
pk_url_kwarg = 'id'
form_class = RecordDetailEditForm
success_url = reverse_lazy('person:home')
You should not override the get_object(..) method here. The get_object(..) method is defined in terms of get_queryset(..): it filters the get_queryset(..) with the given primary key (and/or slug).
You can however easily restrict access by restricting the get_queryset:
class OnlyRecordEditMixin(LoginRequiredMixin):
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.user.is_superuser:
return qs.filter(user=self.request.user)
return qs
or even more generic:
class OnlyRecordEditMixin(LoginRequiredMixin):
user_field = 'user'
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.user.is_superuser:
return qs.filter(**{self.user_field: self.request.user})
return qs
Here we can thus change the user_field to point to the name that is the owner of the object.
This will return a HTTP 404 response if a user that is not a superuser, nor the "owner" of the object aims to update that object.
This is cheaper, since you prevent fetching the object multiple times.

How do I restrict foreign keys choices in admin formset to related objects only in django

How do I limit choices of a ForeignKeyField of an InlineForm in Django Admin, depended on the selected object in the AdminForm.
The problem is that InlineForm does not know anything about the object from the related Admin Form.
You have to pass the parent object from the InlineAdmin to the FormSet to the actual Form. Took me quite a moment to figure that out.
In the example below CalibrationCertificateData is relate to the CalibrationCertificate and I only want to show Quantities that are related with the SensorCategory.
from django.contrib import admin
from django.forms import ModelForm, BaseInlineFormSet
from src.admin import site_wm
from . import models
from quantity_management.models import QuantitySensor
# Register your models here.
site_wm.register(models.CalibrationInstitute, site=site_wm)
class CertDataAdminForm(ModelForm):
class Meta:
model = models.CalibrationCertificateData
fields = "__all__"
def __init__(self, *args, **kwargs):
"""
Make sure that we only show the Quantites that are related to the selected Cert>Sensor>Category
"""
self.parent_obj = None
if 'parent_obj' in kwargs:
self.parent_obj = kwargs['parent_obj']
del kwargs['parent_obj']
super().__init__(*args, **kwargs)
if self.parent_obj:
# Do the actual filtering according to the parent object
category_id = self.parent_obj.Sensor.Category_id
self.fields['Quantity'].queryset = QuantitySensor.objects.filter(Sensor_id=category_id)
class CertDataAdminFormSet(BaseInlineFormSet):
form = CertDataAdminForm
def get_form_kwargs(self, index):
"""
Make sure the form knows about the parent object
"""
kwargs = super().get_form_kwargs(index)
if hasattr(self, 'parent_obj') and self.parent_obj:
kwargs['parent_obj'] = self.parent_obj
return kwargs
class CalibrationDataAdmin(admin.StackedInline):
model = models.CalibrationCertificateData
extra = 0
form = CertDataAdminForm
formset = CertDataAdminFormSet
def get_formset(self, request, obj=None, **kwargs):
"""
give the formset the current object, so it can limit the selection choices according to it
"""
formset = super().get_formset(request, obj, **kwargs)
if formset is not None:
formset.parent_obj = obj
return formset
def formfield_for_foreignkey(self, db_field, request, **kwargs):
print(self.parent_model)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
#admin.register(models.CalibrationCertificate, site=site_wm)
class CalibrationCertificateAdmin(admin.ModelAdmin):
inlines = (
CalibrationDataAdmin,
)

Django: AttributeError: view object has no attribute 'kwargs'

I am building a view that I am passing in a uuid from the url. However when I try to access the kwarg, I get a "AttributeError: view object has no attribute 'kwargs'" error.
In my template, I am passing a UUID:
create/97261b96-23b8-4915-8da3-a90b7a0bdc8e/
The URL:
re_path(
r"^create/(?P<uuid>[-\w]+)/$",
views.DetailCreateView.as_view(),
name="detail_create"),
The View:
class DetailCreateView(SetHeadlineMixin, LoginRequiredMixin, InlineFormSetView):
inline_model = Detail
headline = "Create a Detail"
form_class = DetailForm
success_message = "Detail Added"
template_name = "details/detail_create.html"
def get_object(self, **kwargs):
return Post.objects.get_subclass(uuid=self.kwargs.get('uuid'))
def __init__(self, *args, **kwargs):
super(DetailCreateView, self).__init__(*args, **kwargs)
self.object = self.get_object()
self.model = self.object.__class__()
For context on what is happening -
Post is a model that is an InheritanceManager that other models (Product & Variation) inherit from.
Both models Product & Variation have a manytomanyfield to Detail.
Upon creating a Detail, I will be adding it to either the Product object or Variation object.
To set the model for the InlineFormSetView, I am trying to use the UUID to query for the object and dynamically set that based upon the class of the object I am trying to create a Detail for.
Question
Any ideas why I can't access the kwargs which is being sent in the URL path?
In as_view method kwargs and args attributes are assigned to the view after __init__ method. So when you call get_object inside __init__ it raises the error since self.kwargs is not assigned yet. To fix this error you can move
self.object = self.get_object()
self.model = self.object.__class__()
from __init__ to get_object:
class DetailCreateView(SetHeadlineMixin, LoginRequiredMixin, InlineFormSetView):
inline_model = Detail
headline = "Create a Detail"
form_class = DetailForm
extra = 10
success_message = "Detail Added"
template_name = "details/detail_create.html"
def get_object(self, **kwargs):
self.object = Post.objects.get_subclass(uuid=self.kwargs.get('uuid'))
self.model = self.object.__class__()
return self.object
def __init__(self, *args, **kwargs):
super(DetailCreateView, self).__init__(*args, **kwargs)
Try to use self.request.query_params.get('uuid')

Django: get_object_or_404 for ListView

I have the following ListView. I know about get_object_or_404. But is there a way to show a 404 page if the object doesn't exist?
class OrderListView(ListView):
template_name = 'orders/order_list.html'
def get_queryset(self):
return OrderItem.objects.filter(
order__order_reference=self.kwargs['order_reference'],
)
You can raise a 404 error for a ListView, by changing the allow_empty [django-doc] attribute to False:
class OrderListView(ListView):
template_name = 'orders/order_list.html'
allow_empty = False
def get_queryset(self):
return OrderItem.objects.filter(
order__order_reference=self.kwargs['order_reference'],
)
If we inspect the soure code of the BaseListView (a class that is one of the ancestors of the ListView class), then we see:
class BaseListView(MultipleObjectMixin, View):
"""A base view for displaying a list of objects."""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)
So it also takes pagination, etc. into account, and shifts the responsibility at the get(..) function level.
You can use get_list_or_404:
from django.shortcuts import get_list_or_404
def get_queryset(self):
my_objects = get_list_or_404(OrderItem, order__order_reference=self.kwargs['order_reference'])

How do I use an UpdateView to update a Django Model?

I'm trying to update a model in Django using the class-based generic view UpdateView.
I read the page Updating User model in Django with class based UpdateView to try and get me started, but I'm getting an error 'WSGIRequest' object has no attribute 'id'
I'm a fresh face to Django, so please be forgiving if I'm doing something stupid.
//urls.py
url(r'^portfolios/update/(?P<id>\d+)/$',PortfoliosUpdateView.as_view()),
//views.py
class PortfoliosUpdateView(UpdateView):
form_class = PortfoliosCreateForm
model = Portfolios
template_name = 'portfolios/create.html'
def get(self, request, **kwargs):
self.object = Portfolios.objects.get(id=self.request.id)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object=self.object, form=form)
return self.render_to_response(context)
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.request.id)
return obj
It's mostly just a modified version of the code originally posted, but I thought it'd work. I know that I'm trying to retrieve the id passed as a GET parameter, but that doesn't seem to come through in the request variable. Am I going about this the wrong way?
Thanks
Edit: I think I fixed it, but this may be wrong:
I changed the lines
self.object = Portfolios.objects.get(id=self.request.id)
obj = Portfolios.objects.get(id=self.request.id)
to
self.object = Portfolios.objects.get(id=self.kwargs['id'])
obj = Portfolios.objects.get(id=self.kwargs['id'])
I could be wrong.
It should be:
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.kwargs['id'])
return obj
Look at class based generic view dispatch explains that keyword arguments are assigned to self.kwargs.:
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
self.request = request
self.args = args
self.kwargs = kwargs
return handler(request, *args, **kwargs)
id = self.request.GET.get('id',None) is what you needed when trying to access the GET query string.
However, your view can be simplified:
from django.conf.urls import *
from django.views.generic import UpdateView
from yourapp.models import Portfolios
from yourapp.forms import PortfoliosCreateForm
urlpatterns = patterns('',
url('^portfolios/update/(?P<pk>[\w-]+)$', UpdateView.as_view(
model=Portfolios,
form_class=PortfoliosCreateForm,
template_name='portfolios/create.html',
success_url='/portfolios'
), name='portfolio_update'),
)
views.py
class MyUpdateView(UpdateView):
model = ModelName # required
template_name = 'x/h1.html'
form_class = ModelNameForm
success_url = reverse_lazy('app:page1')
def get_queryset(self):
"""
Optional condition to restrict what users can see
"""
queryset = super().get_queryset()
return queryset.filter(id__lt=20)
def get_success_url(self):
return reverse_lazy(
'app1:abc',
kwargs={'pk': self.object.id}
)
urls.py
In urlpatterns=[]
path('xyz/<pk>/', MyUpdateView.as_view(),name='xyz')
my_model_view.html
{{form}}
You will be able to edit ModelName at url /xyz/<pk>/ where <pk> can be anything from 1 to 20 based on our condition in get_queryset(). Take that condition out to allow users to edit any object.
self.object is only available after post request to the UpdateView.