Using Django CBV's without overriding get_context_data - python

Is there anyway I can take advantage of Django's default CBV's without resulting to super calls on the get_context_data to send extra pieces of info.
For instance, I have a site where the CBV's handle 99% of the work on their own, but I'd like to send minor bits of static info such as page titles and it seems unnecessary to use get_context_data just to accomplish this.
I'm aware that I can do the following:
class Page(DetailView):
model: MyModel
template_name: 'something.html'
title: 'Some Page Title'
And reference title in my template as view.title. However I'd like the page title to be an attribute of my model. Which I'm able to do through the following:
get_context_data(self, **kwargs):
context = super(Page, self).get_context_data(**kwargs)
context['title'] = 'Page Title | %s' % (self.get_object().title)
return context
Is it possible for me to reference the individual model being called in the detail view without running a query or using get_context_data so I can get the model's title attribute and use access it as view.title in my template?

I can't see any reason to define this method just to get the title. The whole point of a DetailView is that you have access to the object in the template, via {{ object }}. So why not just do {{ object.title }}?

Related

How to add information to a field from a function created in a view?

How would I take a value from a view function and put it in a model field? For example, let’s say I did a view that adds two numbers together. How would take that value and add it to the field of a model every time an instance of that model is created? Or is there another way of doing this that I’m not seeing.
I have a function that takes the user's IP and displays the city and region they are in. I was wondering how I could put that information in a model field every time an instance of that model is created.
I think you are talking about get_context_data() which is a method that allows you to pass data to the template.
For example, here is the code from official docs:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
You can then access that context in your template:
{% for book in book_list %}
<p>{{ book.name }}</p>
In your example you would pass the IP to the template or the form depending on what you are trying to do.
Here are official docs that explain it

How to access object count in template using model manager?

I have a model which creates Memo objects. I would like to use a custom Model Manager's posted method to return the total number of Memo objects - then use this number within a template. I am trying to keep as much of my code as possible within my Models and Model Managers and less within my Views as I read that this was a best practice in 'Two Scoops of Django'.
In the shell I can get the number of memos as such:
>>> from memos.models import Memo
>>> Memo.objects.all()
<QuerySet [<Memo: Test Memo 2>, <Memo: Test Memo 1>]>
>>> Memo.objects.all().count()
2
This is what my Model and Model Manager look like:
class MemoManager(models.Manager):
use_for_related_fields = True
def posted(self):
return self.count()
class Memo(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = MemoManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('memos-detail', kwargs={'pk': self.pk})
I know this is clearly the wrong way to do it but I have confused myself here. So how do I use my Model Manager to get the count of objects and use it in a template like: {{ objects.all.count }}?
P.S. I see other posts that show how to do this within the view but as stated I am trying not to use the view. Is using the view required? I also understand my posted method is written incorrectly.
I'm sorry but you have misinterpreted what was written in TSD. The Lean View Fat Model is meant to keep code which pertains to 'business logic' out of the views, and certain model specific things. A request should be handled by a view. So when you want to load a template, you must first have a GET request to your app.
A view function should be written such that Validation of POST data or the Creation of a new object in DB or Querying/Filtering for GET requests should be handled in the corresponding serializer/model/model manager.
What should be happening while you want to load your template.
Have a url for the template that you have created and a view function mapped for it
In the view function you should render said template and pass the necessary data inside the context.
To keep in line with the Lean View Fat Model style, if you want to get a Queryset of of Memo's but only those which have their is_deleted fields set to False, you can overwrite the model manager get_queryset() method for Memo model.
If you want to create a new Memo with a POST request, you can handle
the creation using a ModelForm!
Hope this clears things up!
EDIT:
How to pass a context to a template, in your case the memo count.
def random_memo_view(request):
context = {'memo_count': Memo.posted()}
return render(request, 'template.html', context=context)
RE-EDIT
I just checked that you were using DetailView. In this case follow this from the django docs.
Class Based Views: Adding Extra Context

How to override ModelChoiceField / ModelMultipleChoiceField default widget with a template for each choice

Background
I have two models, Runs and Orders. One run will complete many orders, so I have a Many-to-one relation between my orders and runs, represented as a foreignkey on my orders.
I want to build a UI to create a run. It should be a form in which someone selects orders to run. I'd like to display a list of checkboxes alongside information about each order. I'm using django crispy forms right now.
views.py
class createRunView(LoginRequiredMixin, CreateView):
model = Run
form_class = CreateRunForm
template_name = 'runs/create_run.html'
forms.py
class CreateRunForm(forms.ModelForm):
class Meta:
model = Run
fields = ['orders',]
orders = forms.ModelMultipleChoiceField(queryset=Order.objects.filter(is_active=True, is_loaded=False))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Field('orders', template="runs/list_orders.html"),
Submit('save', 'Create Run'),
Button('cancel', 'Cancel'),
)
Questions
I'm not sure what locals are available to me in the list_orders.html template. It seems like there's {{ field }} and maybe form.visible_fields but if I dig to deeply into either I get a TypeError: 'SubWidget' object is not iterable, which is barely documented online.
The above suggests I might still be getting a widget in the template, despite the fact that Field('orders', template="runs/list_orders.html"), should prevent that, per the crispy docs:
Field: Extremely useful layout object. You can use it to set attributes in a field or render a specific field with a custom template. This way you avoid having to explicitly override the field’s widget and pass an ugly attrs dictionary:
I've seen this answer which suggests using label_from_instance. However I'm not sure how to stuff a bunch of html into label_from_instance. Instead of having a different label, I really want to have a template which generates a bunch of html which shows details about the entire order object, so I'm not sure this approach will work.
The answers in this question mostly confused me, but the accepted answer didn't work, it seems. (maybe a django version issue, or a crispy forms issue?)
TL;DR
How do I render templates with data from each model in ModelMultipleChoiceField?
Widgets control how fields are rendered in HTML forms. The Select widget (and its variants) have two attributes template_name and option_template_name. The option_template_name supplies the name of a template to use for the select options. You can subclass a select widget to override these attributes. Using a subclass, like CheckboxSelectMultiple, is probably a good place to start because by default it will not render options in a <select> element, so your styling will be easier.
By default the CheckboxSelectMultiple option_template_name is 'django/forms/widgets/checkbox_option.html'.
You can supply your own template that will render the details of the orders how you want. IE in your forms.py
class MyCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'myapp/detail_options.html'
class CreateRunForm(forms.ModelForm):
...
orders = ModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
Suppose that myapp/detail_options.html contained the following
{# include the default behavior #}
{% include "django/forms/widgets/input_option.html" %}
{# add your own additional div for each option #}
<div style="background-color: blue">
<h2>Additional info</h2>
</div>
You would see that blue div after each label/input. Something like this
Now, the trick will be how you get the object available to the widget namespace. By default, only certain attributes are present on a widget, as returned by the widget's get_context method.
You can use your own subclass of MultipleModelChoiceField and override label_from_instance to accomplish this. The value returned by label_from_instance is ultimately made available to the widgets as the label attribute, which is used for the visible text in your model form, when it renders {{ widget.label }}.
Simply override label_from_instance to return the entire object then use this subclass for your field.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj
class CreateRunForm(forms.ModelForm):
...
orders = MyModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
So now in the myapp/detail_options template you can use widget.label to access the object directly and format your own content as you please. For example, the following option template could be used
{% include "django/forms/widgets/input_option.html" %}
{% with order=widget.label %}
<div style="background-color: blue">
<h2>Order info</h2>
<p style="color: red">Order Active: {{ order.is_active }}</p>
<p style="color: red">Order Loaded: {{ order.is_loaded }}</p>
</div>
{% endwith %}
And it would produce the following effect.
This also will not disrupt the default behavior of the widget label text wherever widget.label is used. Note that in the above image the label texts (e.g. Order object (1)) are the same as before we applied the change to label_from_instance. This is because the default in template rendering is to use str(obj) when presented with a model object; the same thing that would have previously been done by the default label_from_instance.
TL;DR
Make your own subclasses of ModelMultiplechoiceField to have label_from_instance return the object.
Make your own SelectMultiple widget subclass to specify a custom option_template_name.
The template specified will be used to render each option, where widget.label will be each object.

Django - Changing order_by on click

Using Django and I would like to change the order of the display of the objects on click without refreshing the page.
my model
class IndexView(generic.ListView):
template_name = 'movies/index.html'
page_template = 'movies/all_movies.html'
context_object_name = 'all_movies'
model = Movie
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context.update({
'all_genres': Genre.objects.all(),
'our_pick': Movie.objects.get(pk=259)
})
return context
def get_queryset(self):
return Movie.objects.all()
And this is my index.html
<menu class="menu">
<ul>
<li>Newest</li>
<li>Most Popular</li>
</ul>
</menu>
on clink on Newest, the query will become:
Movie.objects.all().order_by('release_date')
and on click on Most popular, the query will become:
Movie.objects.all().order_by('popularity')
How can I achieve that without refreshing the page? any help would be appreciated!
Your question seems to illustrate a misunderstanding of how front-end and back-end languages work: a click event occurs on the front-end, after the data has been sent from the server. The order_by function is run and completed before the request is completed.
To load new data from the server without reloading, you will have to send a new request in the background using AJAX.
This is probably not a good idea, though, since you are sending the same query set ordered in a different way. I recommend using JS or jQuery to order the list on a click event based on a data attribute that you can set in the list itself. For more information about how to do this, see jquery sort list based on data attribute value.

Why is the get_absolute_url() defined in the models.py?

I am trying to redo my app views with a Class Based Views(CBV) and stumbled across this function get_absolute_url() being defined in the models.py, generic editing views
I have created models and have never used this function before. Is this specific to CBVs?
It's not specific to CBV, you can use it anywhere in your application. It makes it much easier to get the url for a model instance without having to mess around with url resolving. Also it is much easier to get the definitive url for your object in a template when you can call get_absolute_url on the object itself. For example, if you are looping through a list of objects:
{% for post in blog_posts %}
read post
{% endfor %}
That said, there's nothing stopping you using the method in your view either:
post = BlogPost.objects.get(...)
url = post.get_absolute_url()
There's also is nothing at all special about the method though. You can write your own get_foo_url() if you like instead. For example, I wrote a blog post about a get_admin_url, a method to allow you to get the Django admin url to an object:
class Book(models.Model):
...
def get_admin_url(self):
content_type = ContentType \
.objects \
.get_for_model(self.__class__)
return reverse("admin:%s_%s_change" % (
content_type.app_label,
content_type.model),
args=(self.id,))
# {{ book.get_admin_url }}

Categories

Resources