My Django project contains a task manager with Projects and Tasks, I have generic list page showing a list of all projects with information on their total tasks:
class IndexView(generic.ListView):
template_name = 'projects/index.html'
context_object_name = 'project_list'
def get_queryset(self):
"""Return 10 projects."""
return Project.objects.order_by('is_complete')[:10]
I would like to display on my list page the total number of added projects and tasks, but I'm unsure how I should go about this. All my current work has been around listing the number of tasks that are included i each project, but now I want a total - should I add this as a new View? For example, I tried adding this to the view above:
def total_projects(self):
return Project.objects.count()
Then calling {{ project_list.total_projects }} on my template, but it doesn't return anything.
Is Views the correct place to do this?
All by current work has been around listing the number of tasks that are included i each project, but now I want a total - should I add this as a new View?
It depends. If you just want to show the total number of projects and tasks with the first 10 completed projects in the database (which is what your get_queryset method does, be careful), I would go and do all of it in the same view (it would be useless to make a new view only to show some numbers, and that isn't the purpose of ListView IMO).
On the other hand, you're calling a class's instance method (total_projects) from a model's instance. That method doesn't exists on the model, and when an attribute/method doesn't exists in an object when calling it in a template, you just get nothing. Based on the previous paragraph, I would set it in the view's context using get_context_data:
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data["total_projects"] = Projects.objects.count()
# I'm assuming you only need the whole number of tasks with no separation
data["total_tasks"] = Task.objects.count()
return data
Finally, you can edit your get_queryset method and make it be an instance attribute (if you want it to be cleaner and you can handle the filtering with no additional code):
class IndexView(generic.ListView):
queryset = Project.objects.order_by('is_complete')[:10]
I believe it's more common to put function definitions in the Model class (Project, from the looks of it), and add the #property tag above the function.
class Project(models.Model):
''' definitions and stuff '''
#property
def total_projects(self): # etc...
As for your specific case, you could forego the function altogether and just use {{ project_list.count }} or {{ project_list|length }} in your template.
A note about count vs length from the docs:
A count() call performs a SELECT COUNT(*) behind the scenes, so you
should always use count() rather than loading all of the record into
Python objects and calling len() on the result (unless you need to
load the objects into memory anyway, in which case len() will be
faster).
Note that if you want the number of items in a QuerySet and are also
retrieving model instances from it (for example, by iterating over
it), it’s probably more efficient to use len(queryset) which won’t
cause an extra database query like count() would.
So use the correct one for your usage.
Also, according to this answer and the below comment from #djangomachine, length may not always return the same number of records as count. If accuracy is important it may be better to use count regardless of the above case.
Related
I have a project with around 60 models so creating a unique Detail, Create, Update, Delete APIView for each would be a lot of wasted resources (or so it feels like). Would it be better performance-wise (or safe?) to simply create a generic view that could cycle between each model like so?
_CLASSES = <Dictionary of my classes>
class GenericModelView(APIView):
def get(self, request, model_name): # model_name would be a required part of the URL.
model_class = _CLASSES[model_name]
serializer_class = model_class.serializer_class # I would instantiate a serializer for each model_class
return Response(serializer_class(model_class.objects.all()).data)
I think, there should not be any concerns safety wise. However, in my experience, this approach will not last long. You will have to customize the functionality for different model according to the requirements. At that stage, you will need to create separate views. Also, it may become complicated to read and understand logs as always same function is being called. So my final recommendation would be to use different views. It should not take you more than an hour. Just copy and paste. Customize later according to your needs.
Preface:
Let's assume we are working on a DB that stores issues of magazines.
Those issues usually do not have a 'name' per se; instead a whole bunch of attributes (release year, serial number, release months, etc.) will contribute to the name the user may later on identify the issue with.
Depending on the attributes available per issue, this name will be calculated based on a pattern.
For example: an issue from the year 2017 with number 01 will get the name: 2017-01. An issue from the years 2000 and 2001, and the months Jan and Feb will get the name 2000/01-Jan/Feb.
The attributes can be changed at any time.
It is expected that the user can also do queries based on this name - so simply displaying the computed value (through __str__) is not enough.
What I have done so far:
For a long time, I actually calculated the name every time __str__ was called on the issue's instance. It was the quick and dirty (and slow) way.
Querying for the name was very slow and rather complicated and unreliable as it required 'reverse-engineering' the __str__ method and guessing what the user was trying to search for.
Then I tried a hybrid approach, by using a _name model field that is updated if a _changed_flag (f.ex. through signals) is set and the instance is instantiated or saved. This still didn't leave me with an up-to-date name on the database table unless I instatiated every instance that needed updating first. Again, slow. And care had to be taken to not end up in infinite recursions upon calling refresh_from_db (which recreates the current instance in the background).
TL:DR
Right now, I am using a custom QuerySet as a manager for a model with a computed field:
class ComputedNameModel(BaseModel):
_name = models.CharField(max_length=200, editable=False, default=gettext_lazy("No data."))
_changed_flag = models.BooleanField(editable=False, default=False)
name_composing_fields = []
objects = CNQuerySet.as_manager()
# ... some more methods ...
def __str__(self):
return self._name
class Meta(BaseModel.Meta):
abstract = True
QuerySet:
class CNQuerySet(MIZQuerySet):
def bulk_create(self, objs, batch_size=None):
# Set the _changed_flag on the objects to be created
for obj in objs:
obj._changed_flag = True
return super().bulk_create(objs, batch_size)
def filter(self, *args, **kwargs):
if any(k.startswith('_name') for k in kwargs):
self._update_names()
return super().filter(*args, **kwargs)
def update(self, **kwargs):
# it is save to assume that a name update will be required after this update
# if _changed_flag is not already part of the update, add it with the value True
if '_changed_flag' not in kwargs:
kwargs['_changed_flag'] = True
return super().update(**kwargs)
update.alters_data = True
def values(self, *fields, **expressions):
if '_name' in fields:
self._update_names()
return super().values(*fields, **expressions)
def values_list(self, *fields, **kwargs):
if '_name' in fields:
self._update_names()
return super().values_list(*fields, **kwargs)
def _update_names(self):
if self.filter(_changed_flag=True).exists():
with transaction.atomic():
for pk, val_dict in self.filter(_changed_flag=True).values_dict(*self.model.name_composing_fields).items():
new_name = self.model._get_name(**val_dict)
self.filter(pk=pk).update(_name=new_name, _changed_flag=False)
_update_names.alters_data = True
As you can see the, the boilerplate is real. And I have only cherry picked the QuerySet methods that I know I use for now.
Through signals (for relations) or QuerySet methods, a record's _changed_flag is set when anything about it changes. The records are then updated the next time the _name field is requested in any way.
It's blazingly fast, as it does not require a model instance (only the model's classmethod _get_name()) and works entirely off querysets and in-memory data.
Question:
Where to put the call to _update_names() such that the names are updated when required without overriding every single queryset method?
I have tried putting it in:
_clone: bad things happened. To not end up in recursion hell, you would have to keep track of which clone is trying to update and which are there to simply fetch data for the update. Clones are also created upon initializing your app which has a good (tables are always up-to-date) and a bad side (updating without yet having a need for it, costing time). Not all queryset methods create clones and generally, putting your update check in _clone feels too deep.
__repr__: Keeps the shell output up-to-date, but not much more. The default implementation takes a slice of the queryset, disabling the ability to filter, so the updating has to be done before __repr__.
_fetch_all: Like _clone: it runs an update when you may not need it and requires keeping an internal 'you-are-allowed-to-try-an-update' check.
I want to return a single object to the template context so that I can reuse the object several times without haveing to do a query each time.
Here's what I have so far:
def get_context_data(self, **kwargs):
context = super(MessageCreate, self).get_context_data(**kwargs)
cohort = Cohort.objects.filter(members=self.request.user)
context['cohort_member'] = cohort.members.exclude(members=self.request.user)
return context
The error I'm getting is:
'QuerySet' object has no attribute 'members'
So, I'm a little confused. Because I thought the attribute members WAS a part of cohort. If I iterate cohort in the template, I can get to the user.
So, I want that single user, not the entire set.
Any suggestions are welcome.
Thanks!
Your variable cohort is a queryset, not a model instance. To get an actual model instance you have to evaluate the queryset, which you can do various ways - iterating over it, as you mention doing in the template, is one way to do so. A queryset can contain more or less than one instance, of course.
That said, I'm a little puzzled about what you're trying to exclude and include. Is cohort_member supposed to be a set of members of the cohort other than the request user? If so, there are a couple of ways to do that depending on whether you need the cohort for anything other than that set of members in the template and whether the user to cohort relationship is many-to-many or many-to-one. And, if many-to-many, whether you want all other users of all cohorts the user is a member of or whether you want them grouped separately.
I want to have a page counter that displays the number of visitors who have viewed a particular page on my site. Is it possible to do this using Django?
There's a Django app for that problem called django-hitcount. It's easy to use, and reusable in any of your projects.
A "page counter" is what? A persistent piece of data which gets updated by view functions and displayed by a template.
As you are no doubt already aware, all Django things have the following parts.
Model
View Function
Template
Model
If you want to keep the page counter in the database, you need a Django model.
class PageCounter( Model ):
You need to put a row into this model. Usually a "fixture" will help do this, since it's one row and you only put it in once when doing a syncdb.
View Function
Then you need to fetch and update the page counter in your view function.
pageCounter= PageCounter.objects.all()[0]
pageCounter.count += 1
pageCounter.save()
Template
Now you need to provide the value to your templates so it can be displayed.
I know this is an old post but occasionally people might have the same question.
If you want to avoid a third party library and prevent the counter being updated at every page refresh you could do the following Mixin (building on S.Lott's answer)
class BlogPostCounterMixin(object):
def get_context_data(self, **kwargs):
context = super(BlogPostCounterMixin, self).get_context_data(**kwargs)
blog_post_slug = self.kwargs['slug']
if not blog_post_slug in self.request.session:
bp = BlogPost.objects.filter(slug=blog_post_slug).update(counter=+1)
# Insert the slug into the session as the user has seen it
self.request.session[blog_post_slug] = blog_post_slug
return context
It checks if the accessed model has been stored in the session. If it has been stored in the session, it skips incrementing, else it increments the counter and adds the slug of the model to the session preventing increments for page refreshes.
Note: This is a Mixin which you require to add into your view.
For my project I need many "workflow" forms. I explain myself:
The user selects a value in the first field, validates the form and new fields appear depending on the first field value. Then, depending on the others fields, new fields can appear...
How can I implement that in a generic way ?
I think the solution you are looking for is django form wizard
Basically you define separate forms for different pages and customize the next ones based on input in previous screens, at the end, you get all form's data together.
Specifically look at the process step advanced option on the form wizard.
FormWizard.process_step()
"""
Hook for modifying the wizard's internal state, given a fully validated Form object. The Form is guaranteed to have clean, valid data.
This method should not modify any of that data. Rather, it might want to set self.extra_context or dynamically alter self.form_list, based on previously submitted forms.
Note that this method is called every time a page is rendered for all submitted steps.
The function signature:
"""
def process_step(self, request, form, step):
# ...
If you need to only modify the dropdown values based on other dropdowns within the same form, you should have a look at the implemented dajaxproject
I think it depends on the scale of the problem.
You could write some generic JavaScript that shows and hides the form fields (then in the form itself you apply these css classes). This would work well for a relatively small number showing and hiding fields.
If you want to go further than that you will need to think about developing dynamic forms in Django. I would suggest you don't modify the ['field'] in the class like Ghislain suggested. There is a good post here about dynamic forms and it shows you a few approaches.
I would imagine that a good solution might be combining the dynamic forms in the post above with the django FormWizard. The FormWizard will take you through various different Forms and then allow you to save the overall data at the end.
It had a few gotchas though as you can't easily go back a step without loosing the data of the step your on. Also displaying all the forms will require a bit of a customization of the FormWizard. Some of the API isn't documented or considered public (so be wary of it changing in future versions of Django) but if you look at the source you can extend and override parts of the form wizard fairly easily to do what you need.
Finally a simpler FormWizard approach would be to have say 5 static forms and then customize the form selection in the wizard and change what forms are next and only show the relevant forms. This again would work well but it depends how much the forms change on previous choices.
Hope that helps, ask any questions if have any!
It sounds like you want an AJAXy type solution. Checkout the Taconite plugin for jQuery. I use this for populating pulldowns, etc. on forms. Works very nicely.
As for being "generic" ... you might have standard methods on your container classes that return lists of children and then have a template fragmen t that knows how to format that in some 'standard' way.
Ok, I've found a solution that does not use ajax at all and seems nice enough to me :
Create as many forms as needed and make them subclass each other. Put an Integer Hidden Field into the first one :
class Form1(forms.Form):
_nextstep = forms.IntegerField(initial = 0, widget = forms.HiddenInput())
foo11 = forms.IntegerField(label = u'First field of the first form')
foo12 = forms.IntegerField(label = u'Second field of the first form')
class Form2(Form1):
foo21 = forms.CharField(label = u'First field of the second form')
class Form3(Form2):
foo31 = forms.ChoiceField([],
label=u'A choice field which choices will be completed\
depending on the previous forms')
foo32 = forms.IntegerField(label = u'A last one')
# You can alter your fields depending on the data.
# Example follows for the foo31 choice field
def __init__(self, *args, **kwargs):
if self.data and self.data.has_key('foo12'):
self.fields['foo31'].choices = ['make','a','nice','list',
'and you can','use your models']
Ok, that was for the forms now here is the view :
def myview(request):
errors = []
# define the forms used :
steps = [Form1,Form2,Form3]
if request.method != 'POST':
# The first call will use the first form :
form = steps[0]()
else:
step = 0
if request.POST.has_key('_nextstep'):
step = int(request.POST['_nextstep'])
# Fetch the form class corresponding to this step
# and instantiate the form
klass = steps[step]
form = klass(request.POST)
if form.is_valid():
# If the form is valid, increment the step
# and use the new class to create the form
# that will be displayed
data = form.cleaned_data
data['_nextstep'] = min(step + 1, len(steps) - 1)
klass = steps[data['_nextstep']]
form = klass(data)
else:
errors.append(form.errors)
return render_to_response(
'template.html',
{'form':form,'errors':errors},
context_instance = RequestContext(request))
The only problem I saw is that if you use {{form}} in your template, it calls form.errors and so automagically validates the new form (Form2 for example) with the data of the previous one (Form1). So what I do is iterate over the items in the form and only use {{item.id}}, {{item.label}} and {{item}}. As I've already fetched the errors of the previous form in the view and passed this to the template, I add a div to display them on top of the page.