I am using Django's class based generic views in a blog application. One of my views displays a list of posts that have a certain tag. I can write this view as a ListView of posts, filtered by tag. Or I can write this view as a DetailView of the tag, and add the relevant posts to the context.
Is one way more proper -- or Pythonic -- than the other?
The ListView approach seems more semantic, because what I want is a list of posts, but it's also slightly more complex. It requires that I overwrite two methods. The DetailView approach only requires me to overwrite one method.
class PostTagView(ListView):
"""Display all blog posts with a given tag."""
queryset = Post.objects.published()
def get_context_data(self, **kwargs):
context = super(PostTagView, self).get_context_data(**kwargs)
context['tag'] = get_object_or_404(Tag, slug=self.kwargs['slug'])
return context
def get_queryset(self, **kwargs):
queryset = super(PostTagView, self).get_queryset()
return queryset.filter(tags__slug=self.kwargs['slug'])
class TagDetailView(DetailView):
"""Display all blog posts with a given tag."""
model = Tag
def get_context_data(self, **kwargs):
context = super(TagDetailView, self).get_context_data(**kwargs)
context['object_list'] = Post.objects.published().filter(tags__slug=self.kwargs['slug'])
return context
As a rule of thumb, look at the parameters in the URL. If you're using a slug of a Tag then you're most likely dealing with a DetailView and not a ListView.
In this case, the second approach uses less code and it is more elegant. However, it also depends on what you're going to do with the view later on. If you're going to add forms to edit the posts, it might make sense to use a ListView instead. But there's no technical reason to prefer one over the other, it's just that you might end up writing more code in one approach than in the other.
Both ListView and DetailView are not the same technically,
For example you cannot give the path for a DetailView like the below in urls.py,
path('schools_detail/',views.SchoolDetailView.as_view(),name = "detail"),
This will give the below error,
Generic detail view SchoolDetailView must be called with either an
object pk or a slug in the URLconf.
This means that if we have a table called Student and another table called School, we can use the ListView to list all the schools like below,
path('list/',views.SchoolListView.as_view(),name = "list"),
And if we want to list the Schools details for individual school when we click the school icon, then we can use the primary key of the School which Django creates internally and capture it in the url pattern, in my case the url pattern would be "list/{{school.id}}" so to capture this we have to give the path like below for DetailsView,
path('list/<int:pk>/',views.SchoolDetailView.as_view(),name = "detail"),
So Bottom line is you can use the ListView as a normal view for most of the cases, if you want to access another View but only a particular detail in that View which refers with a primary key then you can use DetailsView(the url pattern for the DetailsView will be generated by giving the primary key info in the url, without primary key in the url it wont work since it wont take all the info instead it will only take the info related to the primary key in the url)
Interesting question. Unfortunately, the answer is not quite so interesting: whichever makes the most sense for you and your app. Arguments could be made equally for either approach, so it's really just a judgement call.
The usecase for class based generic views are perfectly described in article:
https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views
In above mentioned article, you'd be able to know when/why and how to use ListView/DetailView along with simple examples.
Related
EDIT:
I know I can change the names of the variables. My question is in the case that I don't want to do that. I want to know what are all the variables that django generates automatically.
I'm doing Django's getting started tutorial and I'm on the generic views section where at one point it explains:
In previous parts of the tutorial, the templates have been provided
with a context that contains the question and latest_question_list
context variables. For DetailView the question variable is provided
automatically – since we’re using a Django model (Question), Django is
able to determine an appropriate name for the context variable.
However, for ListView, the automatically generated context variable is
question_list.
My problem is that I don't know how Django determines this "appropriate names". I want to know this for when I write my own template. I would want to know what context variable names to use in such template.
From what I can understand, if my model is Question, the question context variable would store that question, and the question_list context variable would store every question.
So my doubt is: What other context variables names can I use? and what would they store? I can't seem to find this on the documentation, please redirect me to it if you know where it is.
You can change the question_list to something else by using the context_object_name this isn't explained all that well in that part of the documentation, but ...
Return the context variable name that will be used to contain the list
of data that this view is manipulating. If object_list is a queryset
of Django objects and context_object_name is not set, the context name
will be the model_name of the model that the queryset is composed
from, with postfix '_list' appended. For example, the model Article
would have a context object named article_list.
is given under get_context_object_name method
This is what that method's code looks like, It ought to clear up all doubts:
"""
Get the name of the item to be used in the context.
"""
if self.context_object_name:
return self.context_object_name
elif hasattr(object_list, 'model'):
return '%s_list' % object_list.model._meta.model_name
else:
return None
I think this default context variable name only applies when dealing with Django's Class Based Views.
E.g. If you are using a DetailView for a Animal model, Django will auto create a context variable called 'animal' for you to use in template. I think it also allows the use of 'object'.
Another example is, as you mentioned, the ListView for a Animal model which would generate context name called animal_list.
However, in both of these cases, there are ways to change the default context variable name. If you specify 'context_object_name' in your DetailView, this will be the name you refer to in your template. This will also work for ListViews.
This website has all info on CBVs of all Django versions:
https://ccbv.co.uk/projects/Django/1.9/django.views.generic.detail/DetailView/
I have a form that allow users to log their activities. To make it simple, let's say I only have two fields that I want a user to fill out.
Time
Action
During a day, a user can fill out multiple time + action pairs. I used javascript on the front end to allow users to add these pairs as they wish.
Thus, I do not know how many pairs there will be beforehand. And thus, I cannot create a predefined ModelForm for it.
To deal with this issue, I labeled each Time and Action field with a unique name. So when I receive a POST request, I geta list like this inside the request.POST dictionary:
time_1: 9:50
action_1: wakeup
time_2: 11:00
aciton_2: workout
...
Then, I subtract each pair out of the dictionary and put them into a ModelForm for validation and save to the database.
class TimeActionModel(Model):
time = DateField()
action = CharField(max_length=100)
class TimeActionForm(ModelForm):
class Meta:
model = TimeActionModel
class TimeActionView(View):
def post(self, request, *args, **kwargs):
self._subtract_and_save(request)
def _subtract_and_save(request):
#loop through the request.POST dictionary
#pull out each pair
#stuff each one into a ModelForm object
if form.is_valid():
form.save()
Here is my quesiton:
Does this approach look right to you?
What's the 'Django way' of dealing with such situation?
Thank you!
There is a concept in Django called formset:
A formset is a layer of abstraction to work with multiple forms on the same page. It can be best compared to a data grid.
The Django way would be to use Model formsets:
Like regular formsets, Django provides a couple of enhanced formset classes that make it easy to work with Django models.
Therefore you could create a model formset for your TimeActionModel as such:
from django.forms.models import modelformset_factory
TimeActionFormset = modelformset_factory(TimeActionModel)
You can read more on that in the documentation. It has extensive use cases and examples to cover your case.
UPDATE: The extras parameter of the formset is not quite important. You can easily manipulate the number of extra forms in your formset with a bit of javascript. There are also contrib packages for that such as django-dynamic-formset.
UPDATE2: The name of the fields depends on the prefix used too, which I recommend it in case of many different forms/formsets in a single page, but you can easily deduce it looking at a default form that Django renders.
Also please take not not to forget in your template to include {{ my_formset.management_form }} and {{ my_formsets_form.id }}!
I have a URL conf as /book/(?P<book_id>\d+)/(?P<page_id>\d+)/edit/ wheredjango.views.generic.edit.FormView as handler to this endpoint.
As soon as the request is made, I have to check whether there is a Book object is present with book_id and the page number is present in respective Book using page_id.
Where exactly would be the appropriate place to do this processing? I would also like to store them as instance attributes for further processing wherever required.
Edit
Also, the tricky part here is that the form_class used here is totally dependent on the objects found!
Currently in our function based views, a decorator is used to the method which does the job. In class based views, I am not sure what's the place to do it.
In typical function base view
def get_object_or_404(view_function):
def decorator(request, book_id, page_id):
return view_function(request, book, page)
return decorator
#get_book_or_404
def edit_book(self, book, page):
# process the page
return HttpResponse()
I am basically figuring out the simple approach equivalent of get_book_or_404 for class based views
It doesn't matter, and you shouldn't care, what is the first method called in the view. The point is to put logic where it belongs. In this case, since you're concerned with getting instances, you should probably override get_object. In that method you can return the book object, but also store both book and page objects as instance attributes.
I'm trying to write a class-based view for Django which will utilise the same template each time, regardless of model. The intention is that I can then add a urls.py entry for each model, and not have to bother about a view or a template.
This will be used to display a form, and as the form field names are dependant on model type, the model needs to be examined and field names extracted at the view level, so they can be passed to the generic template. The template then generates the form based on field names and values of the object.
I've been really struggling with this. At the moment I'm working on overriding get_context_data as follows
def get_context_data(self, **kwargs):
context = kwargs
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
#add some custom stuff on too
tempdict = [(field, field.value_to_string(self)) for field in self.object._meta.fields]
#context.update({'datafields' : tempdict})
context.update({ 'blarg': 'tester!!'})
return context
The self.object._meta.fields bit is where I'm haivng the problems. I just can't get my head around how to access the current model. I'm doing this in a view, woud I have aany more luck in a mixin?
Thanks for your time.
O
I think you're going about this the wrong way. Django already knows how to create a form from a model, and forms know how to output themselves. So you can create a standard generic view using ModelFormMixin, there's no need to do anything clever to get form fields for a model's fields.
The only difficult bit is that you want one view to work for multiple models. So, rather than declaring the model explicitly on the view class, you'll need to work out some way of passing it dynamically - perhaps by overriding get_object.
If you're using django 1.3, class based views are included... Just use them and set the 'template_name' attribute to be your "common" name.
https://docs.djangoproject.com/en/1.3/topics/class-based-views/
I know that you can prepopulate admin form fields based on other fields. For example, I have a slug field that is automatically populated based on the title field.
However, I would also like to make other automatic prepopulations based on the date. For example, I have an URL field, and I want it to automatically be set to http://example.com/20090209.mp3 where 20090209 is YYYYMMDD.
I would also like to have a text field that automatically starts with something like "Hello my name is author" where author is the current user's name. Of course, I also want the person to be able to edit the field. The point is to just make it so the user can fill out the admin form more easily, and not just to have fields that are completely automatic.
I know that you can prepopulate some values via GET, it will be something like this
http://localhost:8000/admin/app/model/add/?model_field=hello
I got some problems with date fields but, maybe this could help you.
I recently used Django's ModelAdmin.get_form method for this purpose.
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Yout should be careful about side effects as you are manipulating the base_fields directly.
Django's built-in prepopulated_fields functionality is hardcoded to slugify, it can't really be used for more general purposes.
You'll need to write your own Javascript function to do the prepopulating. The best way to get it included in the admin page is to include it in the inner Media class of a custom Form or Widget. You'll then need to customize your ModelAdmin subclass to use the custom form or widget. Last, you'll need to render some inline Javascript along with each prepopulated field to register the onchange handler and tell it which other field to populate from; I would render this via the custom Widget. To make it nice and declarative you could use a custom ModelAdmin attribute (similar to prepopulated_fields), and override ModelAdmin.formfield_for_dbfield to create the widget and pass in the information about what field it should prepopulate from.
This kind of admin hacking is almost always possible, but (as you can tell from this convoluted summary) rarely simple, especially if you're making an effort to keep your code nicely encapsulated.
I tried a few of these answers and none of them worked. I simply wanted to prepulate a field with another field from a related model. Taking this answer as a starting point, I finally tried to manipulate the model instance object (here obj) directly and it worked for me.
class MyModelAdmin(models.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
if not obj.some_model_field:
obj.some_model_field = obj.related_model.prepopulating_model_field
return form
You can override the default django admin field by replacing it with a form field of your choice.
Check this :
Add custom validation to the admin
I would also like to have a text field
that automatically starts with
something like "Hello my name is
author".
Check out the docs at: http://docs.djangoproject.com/en/dev/ref/models/fields/#default
You could have a CharField() or TextField() in your model, and set this option, which will set the default text. 'default' can also be a callable function.
Something like:
models.CharField(max_length=250, default="Default Text")
The slug handling is done with javascript.
So you have to override the templates in the admin and then populate the fields with javascript. The date thing should be trivial, but I dont know how you should get the logged in users name to the script (not that I have thought very hard but you get the drift :).