In the following code, I don't understand something about the object.
class SingleObjectMixin(ContextMixin):
"""
Provide the ability to retrieve a single object for further manipulation.
"""
model = None
queryset = None
slug_field = 'slug'
context_object_name = None
slug_url_kwarg = 'slug'
pk_url_kwarg = 'pk'
query_pk_and_slug = False
...
...
def get_context_data(self, **kwargs):
"""Insert the single object into the context dict."""
context = {}
if self.object:
context['object'] = self.object
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
context.update(kwargs)
return super().get_context_data(**context)
There is an attribute self.object'in the method get_context_data. I would like to figure out where the attribute `object' is created from.
SingleObjectMixin is a mixin normally used by views. E.g. BaseDetailView uses this mixin where its method get sets the object:
https://github.com/django/django/blob/c8eb9a7c451f7935a9eaafbb195acf2aa9fa867d/django/views/generic/detail.py#L108
The following website gives you a great overview and description of all class based views and some mixins:
https://ccbv.co.uk/projects/Django/4.1/django.views.generic.detail/BaseDetailView/
Related
Some consider model as an object, some consider as an instance. Can anyone tell me what is the difference between these two examples?
model.py:
class ToDo(models.Model):
name = models.CharField(max_length=100)
due_date = models.DateField()
def __str__(self):
return self.name
forms.py:
class ToDoForm(forms.ModelForm):
class Meta:
model = ToDo
fields = ['name', 'due_date']
views.py:
def todo_list(request):
todos = ToDo.objects.all()
context = {'todo_list': todos}
return render(request, 'todoApp/todo_list.html', context)
Considering the code below, what is form instance?
class PostDetailView(DetailView):
model = Post
def post(self, *args, **kwargs):
form = CommentForm(self.request.POST)
if form.is_valid():
post = self.get_object()
comment = form.instance
comment.user = self.request.user
comment.post = post
comment.save()
return redirect('detail', slug=post.slug)
return redirect('detail', slug=self.get_object().slug)
So in object-oriented programming, an object is an instance of a class. So model instance and model object are the same.
Let's do an example for that:
# This is your class
class ToDo(models.Model):
name = models.CharField(max_length=100)
due_date = models.DateField()
# If somewhere I call
my_var = ToDo() # my_var contain an object or an instance of my model ToDo
As for your question about the form, each form in Django may or may not contain an instance. This instance is the object modified by the form. When you create an empty form, this form.instance is None, because your form is not bound to an object. But if you build a form taking an object to modify as its parameter or after filling it, this object is the instance.
Example:
form = CommentForm()
print(form.instance) # This will return None, there is no instance bound to the form
comment = Comment.objects.get(pk=1)
form2 = CommentForm(instance=comment)
print(form2.instance) # Now the instance contain an object Comment or said an other way, an instance of Comment. When you display your form, the fields will be filled with the value of this instance
I hope that it's a little bit more clear.
CommentForm is ModelForm and ModelForm has instance attribute (which you can set (update scenario) or __init__ method of CommentForm will instantiate new model instance of model that you set to Metaclass
from BaseModelForm source:
if instance is None:
# if we didn't get an instance, instantiate a new one
self.instance = opts.model()
object_data = {}
Can Anyone please explain me what this error means??
I have done this in my views.py:
class FormListView(FormMixin, ListView):
def get(self, request, *args, **kwargs):
# From ProcessFormMixin
form_class = self.get_form_class()
self.form = self.get_form(form_class)
# From BaseListView
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty and len(self.object_list) == 0:
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
context = self.get_context_data(object_list=self.object_list, form=self.form)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
class CompanyListView(LoginRequiredMixin,FormListView):
model = Company
form_class = daterangeform
paginate_by = 10
def get_queryset(self):
return company.objects.filter(User=self.request.user)
def get_context_data(self, **kwargs):
context = super(companyListView, self).get_context_data(**kwargs)
context['selectdate_list'] = selectdate.objects.filter(User=self.request.user).latest()
return context
And I am getting this error:
ValueError: earliest() and latest() require either fields as positional arguments or 'get_latest_by' in the model's Meta.
Can anyone please explain me what is wrong in my code and possible solution for doing it in correct way...
Thank you
As specified in the documentation for latest(*fields) [Django-doc]:
Returns the latest object in the table based on the given
field(s).
This example returns the latest Entry in the table, according to the
pub_date field:
Entry.objects.latest('pub_date')
So if you want to obtain the latest object with respect to a field (for example updated_date), you can write this as:
selectdate.objects.filter(
User=self.request.user
).latest('updated_date')
You can however use the latest() without parameters, given you specified the order for this in the Meta class of the model, like:
class Foo(models.Model):
name = models.CharField(max_length=20)
class Meta:
get_latest_by = ['name']
In that case
Foo.objects.latest()
will give the Foo object with the maximum name (if we here compare the names lexicographically).
I'm working on my Django web application and I'm beginning API part.
I have a Create Serializer class like this :
class IndividuCreateSerializer(serializers.ModelSerializer) :
class Meta :
model = Individu
fields = [
'Etat',
'Civilite',
'Nom',
'Prenom',
'Sexe',
'Statut',
'DateNaissance',
'VilleNaissance',
'PaysNaissance',
'Nationalite1',
'Nationalite2',
'Profession',
'Adresse',
'Ville',
'Zip',
'Pays',
'Mail',
'Telephone',
'Image',
'CarteIdentite',
]
def create(self, validated_data):
obj = Individu.objects.create(**validated_data)
IdentityIndividuResumeView.get_context_data(obj.id)
return obj
In this class, I have my create function which should redirect to IdentityIndividuResumeView class when my person is created.
class IdentityIndividuResumeView(LoginRequiredMixin, TemplateView) :
template_name = 'Identity_Individu_Resume.html'
model = Individu
def get_context_data(self, **kwargs) :
context_data = super(IdentityIndividuResumeView, self).get_context_data(**kwargs)
id = self.kwargs['id']
personne = get_object_or_404(Individu, pk=id)
NIU = lib.Individu_Recherche.NIUGeneratorIndividu(personne)
personne.NumeroIdentification = NIU
...
But I don't overcome to pass argument in my function get_context_data. I'm getting this issue :
File "/Users/valentin/Desktop/Identity/api/serializers.py" in create
80. IdentityIndividuResumeView.get_context_data(obj.id)
File "/Users/valentin/Desktop/Identity/views.py" in get_context_data
228. context_data = super(IdentityIndividuResumeView, self).get_context_data(**kwargs)
Exception Type: TypeError at /Api/Identification/create/
Exception Value: super(type, obj): obj must be an instance or subtype of type
EDIT :
It works with FBV model, but I would like to convert this to CBV :
#login_required
def Identity_Individu_Resume(request, id) :
personne = get_object_or_404(Individu, pk=id)
NIU = lib.Individu_Recherche.NIUGeneratorIndividu(personne)
personne.NumeroIdentification = NIU
...
and serializers.py file :
def create(self, validated_data):
obj = Individu.objects.create(**validated_data)
Identity_Individu_Resume(self.context.get('request'), obj.id)
return obj
The create function of the serializer is calling using the class object IdentityIndividuResumeView.get_context_data(obj.id) and not the instance of the class. While in the function based view, you are passing all the required arguments. That's why it is working for it.
Check for your self object at /Users/valentin/Desktop/Identity/views.py line 228 self object is wrong.
I mean Content in the self object is of not proper type.
I have multiple API which historically work using id as the lookup field:
/api/organization/10
I have a frontend consuming those api.
I'm building a new interface and for some reasons, I would like to use a slug instead an id:
/api/organization/my-orga
The API is built with Django Rest Framework. Except the change of lookup field, the api behavior should stay the same.
Is there a solution to allow my API to work with both a slug and a pk ? Those two path should give them same results:
/api/organization/10
/api/organization/my-orga
Here is my API definition:
# urls.py
router = DefaultRouter()
router.register(r'organization', Organization)
urlpatterns = router.urls
#view.py
class Organization(viewsets.ModelViewSet):
queryset = OrganisationGroup.objects.all()
serializer_class = OrganizationSerializer
# serializer.py
class OrganizationSerializer(PermissionsSerializer):
class Meta:
model = Organization
Try this
from django.db.models import Q
import operator
from functools import reduce
from django.shortcuts import get_object_or_404
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
q = reduce(operator.or_, (Q(x) for x in filter.items()))
return get_object_or_404(queryset, q)
Then in View
class Organization(MultipleFieldLookupMixin, viewsets.ModelViewSet):
queryset = OrganisationGroup.objects.all()
serializer_class = OrganizationSerializer
lookup_fields = ('pk', 'another field')
I solved the similar problem by overriding retrieve method and check pk field's value against any pattern. For example if it consists of only numbers.
def retrieve(self, request, *args, **kwargs):
if kwargs['pk'].isdigit():
return super(Organization, self).retrieve(request, *args, **kwargs)
else:
# get and return object however you want here.
I know you asked this question quite a time ago, but here is the complete solution i got from all answers, considering both views and urls:
Put this in your views.py: (With a little edit from drf)
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
if self.kwargs.get(field, None):
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
Then inherit your view from this Mixin and add fields you want to lookup_fields. Like this:
class YourDetailView(MultipleFieldLookupMixin, RetrieveUpdateAPIView):
...
lookup_fields = ['pk', 'slug','code']
And in urls.py:
re_path(r'^organization/(?P<pk>[0-9]+)/$',
YourDetailView),
re_path(r'^organization/(?P<slug>[-a-zA-Z0-9_]+)/$',
YourDetailView),
re_path(r'^organization/sth_else/(?P<code>[0-9]+)/$',
YourDetailView),
class MultipleFieldLookupMixin(object):
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object
class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ('account', 'username')
I think best way is to override the get_object(self) method
class Organization(generics.RetrieveAPIView):
serializer_class = OrganizationSerializer
queryset = Organization.objects.all()
multiple_lookup_fields = ['pk', 'slug']
def get_object(self):
queryset = self.get_queryset()
filter = {}
for field in self.multiple_lookup_fields:
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter)
self.check_object_permissions(self.request, obj)
return obj
There are a lot of answers here already, but none provide a full description including the mixin, view, and url configuration. This answer does.
This is the mixin that works best, it is slightly modified from https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins to not error out on non-existing fields.
class MultipleFieldLookupMixin:
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
Source: https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Modified to not error out for not providing all fields in the url.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs.get(field): # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
Now add the view as follows, it is important to have the Mixin first, otherwise the get_object method is not overwritten:
class RudAPIView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
...
lookup_fields = ['pk', 'other_field']
Now, for the urls, we use default converters. It is important int comes first as that one will actually check if it is an int, and if not fallback to str. If you have more complex fields, you need to resort to regex.
path('efficiency/<int:pk>/', views.RudAPIView.as_view(), name='something-rud'),
path('efficiency/<string:other_field>/', views.RudAPIView.as_view(), name='something-rud'),
I think the fundamental answer is that this would not be good REST/API design and just isn't something DRF would enable.
The official docs have an example for this at https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Also, you need to modify the urls.py adding a new route for the same view, but with the new field name.
If you still would like to use Viewsets without breaking it apart, here you go.
(Test passed on my end)
import operator
from functools import reduce
from django.db.models import Q
from django.shortcuts import get_object_or_404
class MultipleFieldLookupMixin(object):
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filters = {}
pk_fields = ["pk", "id"]
for field in self.lookup_fields:
identifier = self.kwargs[self.lookup_field]
if (field in pk_fields and identifier.isdigit()) or field not in pk_fields:
filters[field] = self.kwargs[self.lookup_field]
q = reduce(operator.or_, (Q(x) for x in filters.items()))
obj = get_object_or_404(queryset, q)
self.check_object_permissions(self.request, obj)
return obj
This is my latest version that supports primary key fields that not necessary are strings, I think is more resilient.
import operator
from functools import reduce
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.core.exceptions import ValidationError
class MultipleFieldLookupMixin:
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filters = {}
for field in self.lookup_fields:
try:
# Validate the data type we got is a valid data type for the field we are setting
self.get_serializer_class().Meta.model._meta.get_field(field).to_python(
self.kwargs[self.lookup_field]
)
filters[field] = self.kwargs[self.lookup_field]
except ValidationError:
continue
query = reduce(operator.or_, (Q(x) for x in filters.items()))
obj = get_object_or_404(queryset, query)
self.check_object_permissions(self.request, obj)
return obj
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
profile = get_object_or_404(Profile, user__username=self.request.user)
context['profile'] = profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
profile = self.get_context_data()['profile']
return super(CourseListView, self).get_queryset().filter(creator=profile)
I have the following two class-based-views. CourseListView inherits ProfileContextMixin which I wrote so that I don't have to repeat overriding get_context_data to get the profile every time in my other views.
Now in my CourseListView, I need to filter the result based on the creator argument, which is the same one retrieved in get_context_data
I know my get_queryset works, and it will call get_context_data() to get the profile, but this will also cause my get_context_data to be called twice, executing the same SQL two times.
Is there a way I can access the context efficiently?
UPDATE:
After reading ListView method flowchart, I ended up doing this, but not sure if it's the best way. Feedback is appreciated.
object_list = []
context = None
def get_context_data(self, **kwargs):
return self.context
def get_queryset(self):
self.context = super(CourseListView, self).get_context_data()
profile = self.context['profile']
queryset = super(CourseListView, self).get_queryset()
queryset = queryset.filter(creator=profile)
self.context['object_list'] = queryset
return queryset
You can move getting profile out of get_context_data to upper function, like dispatch, or use cached_property decorator. This way your profile will be stored in _profile argument of view and you will not do second get to DB after calling self.profile second time.
from django.utils.functional import cached_property
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
#cached_property
def profile(self):
return get_object_or_404(Profile, user__username=self.request.user)
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
context['profile'] = self.profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
return super(CourseListView, self).get_queryset().filter(creator=self.profile)