Django needs to restart server for changing data in template - django

The goal is to make texts in static templates like "about us" page dynamic with a model so that it can be edited easily later on.
My model is as below:
class SiteData(models.Model):
data_set_name = models.CharField(
max_length=200, blank=False, null=False, default="نام مجموعه داده"
)
main_page_english_info = models.TextField(
verbose_name="مقدمه انگلیسی", blank=True, null=True
)
main_page_persian_info = models.TextField(
verbose_name="مقدمه فارسی", blank=True, null=True
)
my view is as below:
class HomePageView(ListView):
model = SiteData
template_name: str = "core/index.html"
site_data = SiteData.objects.get(pk=1)
context_vars = {
"site_data": site_data,
}
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context.update(HomePageView.context_vars)
return context
and my template:
<p class="mt-3 text-justified text-english fw-semibold nm-text-color"
style="direction: ltr; ">{{ site_data.main_page_english_info }}</p>
<p class="mt-3 text-justified nm-text-color">{{ site_data.main_page_persian_info }}</p>
My problem with this method is two main issues:
This method works and if I change data from admin panel, it will be applyed, BUT ONLY if I restarted development server! (ctrl + c and py manage.py runserver). Where is my problem? How can I solve this issue?
My model itself isn't so dynamic itself, and this is why I'm also trying another method which I have other problems in that, and I've asked another question here.
Now, I'll appreciate an answer to either of these question!

You are fetching the SiteData instance when the server starts, instead of during the request. Move the query into the get_context_data method, E.g.
class HomePageView(ListView):
model = SiteData
template_name: str = "core/index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["site_data"] = self.model.objects.get(pk=1)
return context
Original answer follows:
By default, the cached loader is used (see the loaders section here), which avoids django needed to read and parse the template for every request.
I recommend leaving this enabled, and restarting as required. However, if you absolutely need this, you can change the configuration using the TEMPLATE variable in your settings file. You can see the available options in the official documentation

Related

Filter Queryset in Wagtail ModelAdmin not working

I have a menu item that contains 4 resources, each language, if the user goes to EnResources i would like it to only display the Resources where the language field contains 'en' and the same with the other languages. So the issue is it is only ever getting the en items, no matter which menu item i choose its always the en items, not the FrResources or anything.
I am following the docs http://docs.wagtail.io/en/v2.5.1/reference/contrib/modeladmin/indexview.html#modeladmin-get-queryset
Models.py
class Resource(models.Model):
language = models.CharField(max_length=255, choices=constants.LANGUAGES)
title = models.CharField(blank=True, max_length=255)
resource_type = models.CharField(
choices=constants.RESOURCE_TYPES,
max_length=255
)
description = models.TextField()
link = StreamField(
blocks.BasicLinkBlock(max_num=1),
blank=True,
)
panels = [
FieldPanel('language'),
FieldPanel('title'),
FieldPanel('resource_type'),
FieldPanel('description'),
StreamFieldPanel('link'),
]
constants.py
RESOURCE_TYPES = (
('Documentation', 'Documentation'),
('Whitepaper', 'Whitepaper'),
('Webinar', 'Webinar'),
('Video', 'Video'),
('Testimonial', 'Testimonial'),
('ProductSheet', 'ProductSheet'),
)
LANGUAGES = (
('en', 'English'),
('fr', 'French'),
('be-fr', 'Belgique'),
('be-nl', 'Nederlands'),
)
WagtailHooks.py
class ResourceAdmin(ModelAdmin):
model = models.Resource
menu_label = 'Resources'
menu_icon = 'snippet' # change as required
list_display = (
'resource_type',
'title',
)
list_filter = (
'resource_type',
)
search_fields = (
'title',
'business_email',
)
class EnResourceAdmin(ResourceAdmin):
menu_label = 'English Resources'
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language='en')
class FrResourceAdmin(ResourceAdmin):
menu_label = 'French Resources'
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language='fr')
class BeResourceAdmin(ResourceAdmin):
menu_label = 'Belgium Resources'
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language='be-fr')
class NlResourceAdmin(ResourceAdmin):
menu_label = 'Nederlands Resources'
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language='be-nl')
class ResourceAdminGroup(ModelAdminGroup):
menu_label = 'Resources'
menu_icon = 'snippet' # change as required
menu_order = 1000 # (000 being 1st, 100 2nd)
items = (
EnResourceAdmin,
FrResourceAdmin,
BeResourceAdmin,
NlResourceAdmin,
)
modeladmin_register(ResourceAdminGroup)
EDIT:
I started doing a little more research and i found that according to the Django docs on default_manager.
https://docs.djangoproject.com/en/2.2/topics/db/managers/#django.db.models.Model._default_manager
If you use custom Manager objects, take note that the first Manager
Django encounters (in the order in which they’re defined in the model)
has a special status. Django interprets the first Manager defined in a
class as the “default” Manager, and several parts of Django (including
dumpdata) will use that Manager exclusively for that model. As a
result, it’s a good idea to be careful in your choice of default
manager in order to avoid a situation where overriding get_queryset()
results in an inability to retrieve objects you’d like to work with.
You can specify a custom default manager using
Meta.default_manager_name.
If you’re writing some code that must handle an unknown model, for
example, in a third-party app that implements a generic view, use this
manager (or _base_manager) rather than assuming the model has an
objects manager.
Note the last part of the first paragraph. I think that is exactly what is happening here.
Your approach is correct. After you run your code, you will see 4 menu items with the names you have given, but as you said, all those pages will show the same data.
If you closely look at the URL that your browser has gone to, you will see that no matter which menu item you click, browser redirects to the same URL. You need to fix that.
To do that you have to make use of url_helper_class variable. That accepts an instance of AdminURLHelper class. You can find the source code of that class here.
If you read the source code, you will see that this class is generating urls using the app_label and model_name. But as those two are same in this instance, no matter which menu item you click, this generates the same URL.
You can override all these methods to perform a custom URL generation as you like. But there is a simpler hack.
If you looked at the source code, you will see that the app_label and the model_name is taken from the model's meta data. As those are the only two things that this class is getting from the meta data, you can simply override it with a custom class.
class MyOpts:
def __init__(self, model_name, app_label) -> None:
self.model_name = model_name
self.app_label = app_label
class MyUrlHelper(AdminURLHelper):
def __init__(self, model):
super().__init__(model)
self.opts = MyOpts(XXXX, model._meta.app_label)
Instead of passing the model_name from meta data, you have to pass something else (stated as XXXX in the code) to make URLs unique for each menu item.
def get_helper(name):
class MyUrlHelper(AdminURLHelper):
def __init__(self, model):
super().__init__(model)
model_name = f"{model._meta.model_name}-{name}"
self.opts = MyOpts(model_name, model._meta.app_label)
return MyUrlHelper
class EnResourceAdmin(ResourceAdmin):
menu_label = 'English Resources'
url_helper_class = get_helper('en')
Now you will see that the menu items redirects to different pages and your filter have applied correctly. Hope this helps!!
This is a great question and I had a similar issue recently.
You are correct that the first manager will be used as the default manager. A good way to get the outcome you are looking for is to define proxy models for each case of Resource, and add custom managers for each of the proxy models. Then you can modify get_queryset to return only instances where language = 'some language'.

Django: while displaying list of articles show favourite status of each articles based on the user

I have List of articles to be displayed as per date. Every User can mark any article as his favourite.
If a user is logged in, then he should be able to see some indication whether the article is already marked as his favourite or not.
the following are my models:
class Favourite(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True)
article = models.ForeignKey(Article, null=True,on_delete=models.CASCADE)
class Meta:
unique_together = ('user', 'article')
class Article(models.Model):
title = models.CharField(max_length=256)
intro_image = models.ImageField(null=True, blank=True, upload_to=doc_hash)
description = models.TextField(null=True, blank=True)
I was thinking for every article get all the user ids who marked that article as favourite and then later check it at front end with the current user.
But if some article is marked as favourite by 1000 users , then unneccessarily i will have to get the data of 1000 users along with that article which is too much.
Is there a way if i pass the user i can get the favourite data only with respect to that user for each article so that i can save both on queries and amount of data to be passed to front end.
Either do that in your view's context or as a custom template tag.
The examples below are dry-coded, so they might have silly bugs.
In a view (assuming a class-based view):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['is_favourite'] = Favourite.objects.filter(user=self.request.user, article=self.object).exists()
return context
Usage:
{% if is_favourite %}Yay, you like it!{% endif %}
or a template tag:
#register.simple_tag(takes_context=True)
def is_favourite(context, article):
request = context['request']
return Favourite.objects.filter(user=request.user, article=article).exists()
Usage:
{% is_favourite article as fav %}
{% if fav %}Yay, you like it!{% endif %}
Edit
For a ListView, you can do something like
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['favourited_ids'] = set(Favourite.objects.filter(user=self.request.user, article__in=context['object_list']).values_list('article_id', flat=True))
return context
and use it
{% if article.id in favourited_ids %}Yay, you like this article!{% endif %}
I assume that you need a flag to state if the user has already marked the article as favourite or not, suppose there are 100 articles and out of that a user has marked 40 articles as favourite then when the data would be sent then 100 articles data is sent with 40 articles having flag as read TRUE and rest as FALSE.
Following is the SQL equivalent, which you can convert to Django ORM as per your need. 'xyz' is the user id for which you need to show the articles
SELECT article.*,
CASE
WHEN read.article_id IS NOT NULL then TRUE
ELSE FALSE
as read_flag
from Article as article
left join
(select article_id from Favourite where user_id = 'xyz') as read
on article.id = read.article_id
I found the below answer from: https://stackoverflow.com/a/51889455/2897115. Using annotations and subquery. Django can be faster using annotations and subquery which i read from. https://medium.com/#hansonkd/the-dramatic-benefits-of-django-subqueries-and-annotations-4195e0dafb16
I am putting the solution given in https://stackoverflow.com/a/51889455/2897115. Using annotations
qs = Post.objects.all()
sub = Like.objects.filter(post__id=OuterRef('pk'), user=request.user)
values = qs.annotate(
date=Func(F('date'), function='db_specific_date_formatting_func'),
current_user_like=Exists(sub)
).values('text, 'date', 'current_user_like')

Django-cms and autoblocks

I am working with online shop project. In product model, i have description field but it's empty. Somehow description data is stored in django-cms and autoblocks. From page, i can edit that description with django-cms. In template tag {% autoblock product.slug %} line is description.
In views.py, i have 'product' passed as context, but it's slug field has nothing to do with description. Also, if i write {{ product.slug }}, it gives me slug.
Also googled about Autoblocks, but what managed to find out it's this model:
class Autoblock(models.Model):
composite_id = models.CharField(max_length=150)
content = PlaceholderField('content')
site = models.ForeignKey(Site)
def __unicode__(self):
return self.composite_id
All of these fields has nothing to do with description.
Im struggling for long time to find where that description is. Any hints would be appreciated.
EDITED:
Product model:
class HBaseProduct(Product):
def __init__(self, *args, **kwargs):
super(HBaseProduct, self).__init__(*args, **kwargs)
image = models.ImageField(upload_to='images/', blank=True, max_length=300, verbose_name=_('Image'))
position = models.IntegerField(default=0, null=False)
description = models.TextField(null=True, blank=True)
Your description is in Autoblock.content, which is a ForeignKey to 'cms.Placeholder', which in turn holds a tree of 'cms.CMSPlugin' models in cmsplugin_set.
There's currently no straight-forward way to turn a Placeholder into a string (of HTML) to be used somewhere else outside of a request/response cycle.
Your best bet is to call Placeholder.render with a context object that holds a (fake) Django Request object. This will return the rendered contents and you can then store that in description.

Form within a form in Django?

I have been looking at the documentation and thought maybe inline-formsets would be the answer. But I am not entirely sure.
Usually whenever you create a ModelForm it is bound to the related Model only. But what if you wanted to edit two models within a form?
In a nutshell, when editing the class conversation, and selecting a Deal class from the dropdown, I would like to be able to change the status of the selected deal class as well (but not the deal_name). All within the same form. Does Django allow that?
class Deal(models.Model):
deal_name = models.CharField()
status = models.ForeignKey(DealStatus)
class Conversation(models.Model):
subject = models.CharField()
deal = models.ForeignKey(Deal, blank=True, null=True)
Update:
The reason I wasn't sure if inline-formssets are the answer is the following behaviour:
View:
call = get_object_or_404(contact.conversation_set.all(), pk=call_id)
ConversationFormSet = inlineformset_factory(Deal, Conversation)
fset = ConversationFormSet(instance=call)
variables = RequestContext(request, {'formset':fset})
return render_to_response('conversation.html', variables)
Template
{{ formset }}
The result I am getting is not what I expected. I am getting three forms of Conversation class, where the first one is filled out (due editing and passing in the isntance). However the Deal DropDown menu is not listed at all. Why?
I found the solution and hope this will help someone else with the same problem in the future. I ended up redesigning my models.
I simply added the status also to my Conversation model.
class Conversation(models.Model):
subject = models.CharField()
deal = models.ForeignKey(Deal, blank=True, null=True)
status = models.ForeignKey(DealStatus)
In the view I added a custom save like this:
if form.is_valid():
call = form.save(commit=False)
deal = get_object_or_404(Deal.objects.all(), pk=call.deal.id)
deal.status = call.status
deal.save()
call.save()
That works nicely.
Another approach is to use signal like this:
def update_deal_status(sender, instance, created, **kwargs):
if created:
deal = Deal.objects.get(id__exact=instance.deal_id)
deal.status = instance.status
deal.save()
signals.post_save.connect(update_deal_status, sender=Conversation)

Viewing subset of objects in Django, Views, URLs, Models

I know this is a very basic concept in Django, and I have tried the tutorial but it is not working. I am working on a comic book database with the models set like this (at least, a sample of two of the models):
Class Title(models.Model):
title = models.CharField(max_length=256)
vol = models.IntegerField("Vol.")
slug = models.SlugField(blank=True, null=True)
#desc = models.CharField(max_length=256)
class Meta:
ordering = ['title']
def get_absolute_url(self):
return "/comics2/title/%s" % self.slug
def __unicode__(self):
return self.title
class Issue(models.Model):
title = models.ForeignKey(Title)
number = models.IntegerField(help_text="Enter the number only. Do not include the hashtag.")
writer = models.ManyToManyField(Creator)
What I am trying to do is create a page that shows a list of all the issues within that Title.
But, I have it setup in the views like this:
class AstonishingXMenIssueListView(ListView):
context_object_name = "astonishing_list"
queryset = Issue.objects.filter(title__title="Astonishing X-Men").order_by("number")
template_name = "comics2/astonishing_list.html"
My urls.py look like this:
(r'^comics2/title/(?P<title_slug>[-\w]+)/$', AstonishingXMenIssueListView.as_view(
)),
Of course, going to /uncanny-xmen-v1/ shows the same thing as the Astonishing link above.
Obviously, this is not a practical way to list issues by title for future issues and titles, so I need it setup so that I don't have to individually do this. Now, I have tried following the Django generic views tutorial, but I got an index tuple error.
I've tried this, but it doesn't work. This is what gets me the index tuple error.
class IssuesByTitleView(ListView):
context_object_name = "issues_by_title_list"
template_name = "comics2/issues_by_title.html",
def get_queryset(self):
title = get_object_or_404(Title, title__iexact=self.args[0])
return Issue.objects.filter(title=title)
Any ideas? And can someone please reply in baby-language, as I am new to Django and Python, so simply telling me to look at the Tutorial again isn't going to help. So, maybe writing out the code would help! Thanks!
Generally, your IssueByTitleView is the right way to do it. But as you use named groups in your URL regex (the (?P<title_slug>[-\w]+) part of your URL), you have to access the URL parameters through self.kwargs instead of self.args. Also, you have to filter on the slug field, not the title field:
title = get_object_or_404(Title, slug=self.kwargs['title_slug'])