Django sitemaps with no associated object (just a view) - django

I'm setting up some Django sitemaps. It works really well for all the objects I have, but I'm curious about how I should do it if I'd like to put something in the sitemap that has no object associated with it.
For instance, I have a category listing, and I can just return a queryset of all categories. URLs would be example.com/cats/12 or what have you. I also have a sort of pseudo root category that isn't associated with a category object. That page (example.com/cats/) is just a view that includes all sub categories with no parent, and a list of products. Point is, I can't use get_absolute_url because there is no "root" object. My solution was to get the queryset as a list, add a "None" object, then get the appropriate URL:
class CatsSitemap(Sitemap):
changefreq = "weekly"
priority = 0.4
def items(self):
cats = list(Category.objects.all())
cats.append(None)
return cats
def location(self, obj):
if(obj != None):
return reverse('cats_sub_category', args=[obj.pk])
else:
return reverse('cats_root')
Does anyone see a problem with this? Will returning them as a list kill performance? Realistically we'll have perhaps hundreds of categories, but probably not many more than that. Too much?

Maybe I found a better way to do it, which will certainly be useful in the future with other named views that don't have associated objects. In a sitemaps.py, put:
class NamedURLSitemap(Sitemap):
priority = 1.0
changefreq = "daily"
def __init__(self, names):
self.names = names
def items(self):
return self.names
def lastmod(self, obj):
return datetime.datetime.now()
def location(self, obj):
return reverse(obj)
And in the urls.py, put:
'cat-roots': NamedURLSitemap(['cats_root']),
This simplifies the Sitemap code for categories:
class CatsSitemap(Sitemap):
changefreq = "weekly"
priority = 0.4
def items(self):
return Category.objects.all()
def location(self, obj):
return reverse('cats_sub_category', args=[obj.pk])
Any opinion on this or the former solution?

Slight improvement to your answer which includes the priority and changefreq for each page as part of the list passed to init:
class NamedURLSitemap(Sitemap):
def __init__(self, pages):
"""
Parameters:
``pages``
A list of three-tuples containing name, priority, and changefreq:
e.g. [('home', 0.5, 'daily'), ('search', 0.5, 'never')]
"""
self.pages = pages
def items(self):
return self.pages
def lastmod(self, obj):
return datetime.datetime.now()
def location(self, obj):
return reverse(obj[0])
def priority(self, obj):
return obj[1]
def changefreq(self, obj):
return obj[2]

Related

cached_property and classmethod doesnt work together, Django

I am trying to use cached_property and classmethod decorators together in a viewset but it doesnt work regardless of their mutual position. Is it any chance to make it work together or cached_property doesnt work with classmethod?
Tnanks.
#cached_property
#classmethod
def get_child_extra_actions(cls):
"""
Returns only extra actions defined in this exact viewset exclude actions defined in superclasses.
"""
all_extra_actions = cls.get_extra_actions()
parent_extra_actions = cls.__base__.get_extra_actions()
child_extra_actions = set(all_extra_actions).difference(parent_extra_actions)
return (act.__name__ for act in child_extra_actions)
For cached property with classmethod usage i wrote that code a few days ago:
from django.utils.decorators import classproperty
class cached_classproperty(classproperty):
def get_result_field_name(self):
return self.fget.__name__ + "_property_result" if self.fget else None
def __get__(self, instance, cls=None):
result_field_name = self.get_result_field_name()
if hasattr(cls, result_field_name):
return getattr(cls, result_field_name)
if not cls or not result_field_name:
return self.fget(cls)
setattr(cls, result_field_name, self.fget(cls))
return getattr(cls, result_field_name)
It will be caching result in class-level.
Usage is similar as classproperty:
#cached_classproperty
def some_func(cls, *args, **kwargs):
...
If you do not have django in dependencies, then you may want prevent classproperty parent usage (sources). In that case you may use that decorator:
class cached_classproperty(classproperty):
def __init__(self, method=None):
self.fget = method
def get_result_field_name(self):
return self.fget.__name__ + "_property_result" if self.fget else None
def __get__(self, instance, cls=None):
result_field_name = self.get_result_field_name()
if hasattr(cls, result_field_name):
return getattr(cls, result_field_name)
if not cls or not result_field_name:
return self.fget(cls)
setattr(cls, result_field_name, self.fget(cls))
return getattr(cls, result_field_name)
def getter(self, method):
self.fget = method
return self

Django tutorial. Generic views. context_object_name = 'latest_question_list'

I'm a bit confused with Django generic views. As shown in here we are converting custom views into generic views. And while I understand what happens in DetailView and ResultsView, I don't entirely grasp how this:
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return render(request, 'polls/index.html', context)
converts into this:
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
In the first example, latest_question_list = Question.objects.order_by('-pub_date')[:5]
But in the second example, what latest_question_list variable is equal to in here? We haven't even defined it..
Can anyone please shed some light into this?
A ListView behind the curtains performs a lot of operations to create a context and pass that to a render engine. We can take a look at the implementation through Classy Class-Based Views.
In essence when you trigger such class-based view you will, depending on the HTTP method, trigger the get(..), post(..), etc. method.
The get(..) method is defined by the BaseListView class, and defined as:
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)
The import part is that we first the result of get_queryset() to self.objects_list, and later construct a context with self.get_context_data(). We then make a call to self.render_to_response(..) which basically will use the specified template, and render it with the given context.
The get_context data has two parents with an implementation. The most basic (highest in the inheritance hierarchy) is that of the ContextMixin, but this function does not do much:
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
It simply takes the dictionary constructed by the keyword arguments (empty if there are no keyword arguments, which is the case here), and it adds an extra key 'view' that is associated with self. It also can add extra key-value pairs that can be defined in self.extra_context, but we can ignore that here.
The most interesting logic is implemented in the MultipleObjectMixin:
def get_context_data(self, *, object_list=None, **kwargs):
"""Get the context for this view."""
queryset = object_list if object_list is not None else self.object_list
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super().get_context_data(**context)
What happens here is that we first assign self.object_list, the variable that we first have set with the outcome of self.get_queryset to a local variable named queryset. We then will paginate that queryset, but that is not very relevant for your question.
We then obtain the name by calling self.get_context_object_name(queryset). By default this is implemented as:
def get_context_object_name(self, object_list):
"""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
So if you have set the context_object_name attribute, like you did, then it will simply return that name. So we can conclude that in the get_context_data(..), method, the context_object_name will have the name you privided, here 'latest_question_list'.
We then keep processing the code in get_context_data(..): we construct a dictionary, and at the bottom we check if context_object_name is not None. If that is the case, we associate the queryset with that key (so here with 'latest_question_list'). Eventually when the correct context dictionary is constructed, we make a super() call with the constructed context as **kwargs, and as we discussed before, the ContextMixin, will simply return that dictionary with very small changes.
So at the end the context will have the name of your list (here 'latest_question_list') associated with the queryset, and it will render the template with that context data.
In class based view you used context_object_name = 'latest_question_list'
That why it's similar to latest_question_list, you used in function based view.
In class based view, if you don't add context_object_name then it's value automatically object_list.
Something like context_object_name='object_list'.
TL;DR version of the accepted answer.
Under the hood, Django does a lot of things in the ListView generic view.
For such a view:
class IndexView(generic.ListView):
model=Question
The automatically generated context variable is question_list.
If you want to override it, you must use context_object_name variable to set a name for your custom context variable. That's all it is, just setting a name.
Then you must use the get_queryset predefined function, which will relate its output to the context_object_name variable.
Thus, it is important to use these particular names for the variable and the function.

Get Django CMS plugin information from Haystack search results

My search results page needs to display information about the Plugins where the query was found, too. I found this question with a similar problem, but I don't only need the contents, I need to know stuff about the plugin - i.e. what's it called, where it is on the page and stuff. Basically I would like a reference to the plugin where the query was located, but I can only find information about the page and title. I haven't been able to find it anywhere on the SearchQuerySet object and in the vicinity - also coming up empty in the documentation for Haystack. Is it possible and how?
Stack I'm using: Elasticsearch 2.4, django-haystack 2.8, aldryn-search 1.0 (for CMS indexing).
I ended up writing a new index for CMSPlugins. Not sure how much use my code is, but maybe it'll help someone out.
from django.conf import settings
from aldryn_search.helpers import get_plugin_index_data
from aldryn_search.utils import clean_join, get_index_base
from cms.models import CMSPlugin
class CMSPluginIndex(get_index_base()):
haystack_use_for_indexing = True
index_title = True
object_actions = ('publish', 'unpublish')
def get_model(self):
return CMSPlugin
def get_index_queryset(self, language):
return CMSPlugin.objects.select_related(
'placeholder'
).prefetch_related(
'placeholder__page_set'
).filter(
placeholder__page__publisher_is_draft=False,
language=language
).exclude(
plugin_type__in=settings.HAYSTACK_EXCLUDED_PLUGINS
).distinct()
def get_search_data(self, obj, language, request):
current_page = obj.placeholder.page
text_bits = []
plugin_text_content = self.get_plugin_search_text(obj, request)
text_bits.append(plugin_text_content)
page_meta_description = current_page.get_meta_description(fallback=False, language=language)
if page_meta_description:
text_bits.append(page_meta_description)
page_meta_keywords = getattr(current_page, 'get_meta_keywords', None)
if callable(page_meta_keywords):
text_bits.append(page_meta_keywords())
return clean_join(' ', text_bits)
def get_plugin_search_text(self, base_plugin, request):
plugin_content_bits = get_plugin_index_data(base_plugin, request)
return clean_join(' ', plugin_content_bits)
def prepare_pub_date(self, obj):
return obj.placeholder.page.publication_date
def prepare_login_required(self, obj):
return obj.placeholder.page.login_required
def get_url(self, obj):
parent_obj = self.ancestors_queryset(obj).first()
if not parent_obj:
return obj.placeholder.page.get_absolute_url()
return # however you get the URL in your project
def get_page_title_obj(self, obj):
return obj.placeholder.page.title_set.get(
publisher_is_draft=False,
language=obj.language
)
def ancestors_queryset(self, obj):
return obj.get_ancestors().filter(
plugin_type=# Some plugins that I wanted to find
).order_by(
'-depth'
)
def get_title(self, obj):
parent_obj = self.ancestors_queryset(obj).first()
if not parent_obj:
return self.get_page_title_obj(obj).title
return # get title from parent obj if you want to
def prepare_site_id(self, obj):
return obj.placeholder.page.node.site_id
def get_description(self, obj):
return self.get_page_title_obj(obj).meta_description or None
If you are using aldryn-search, you only need to define in PLACEHOLDERS_SEARCH_LIST all the placeholders you want to check, therefore all plugins inside will be checked:
PLACEHOLDERS_SEARCH_LIST = {
'*': {
'include': ['content'],
'exclude': [''],
},
}

Django sitemaps, two pages using the same object

I am trying to add a sitemap to a jobsite I have built (first time using the sitemaps framework in Djnago, so not 100% sure what I should be doing).
Anyway, I have two pages, a "job detail" page, and a "job apply" page. These are both based on the Job model, and have urls referencing the job id.
urls.py
url(r'^job-details/(?P<pk>\d+)/$', views.JobDetailView.as_view(), name='job_detail' ) ,
url(r'apply/(?P<pk>\d+)/$', views.JobApplyView.as_view(), name='job_apply' ),
sitemap.py
class JobsSitemap(Sitemap):
changefreq = "daily"
priority = 0.5
def items(self):
return Job.objects.filter( active=True,
venue__contacts__account_verified=True,
date_expires__gte=date.today())
def lastmod(self, obj):
return obj.date_posted
models.py
class Job(models.Model):
... field definitions here ....
def get_absolute_url(self):
return reverse('job_detail', kwargs={'pk': self.id})
Now the problem is that I have already specified get_absolute_url() on the Job model to point to the job detail page. So I can't point it to the job_apply page as well.
I tried configuring it according to the "Sitemap for static views" section in the docs, but that complains that there is no reverse match for that URL (as it is expecting the kwargs argument).
What is the correct way to deal with pages based on the same object?
There maybe other ways of doing it, but one that's really simple is to create two SiteMaps. Let the current one be as it is and create a new one, this time make sure to override the [location][1] property.
Location Optional. Either a method or attribute.
If it’s a method, it should return the absolute path for a given
object as returned by items().
If it’s an attribute, its value should be a string representing an
absolute path to use for every object returned
class JobsApplySitemap(Sitemap):
changefreq = "daily"
priority = 0.5
def items(self):
return Job.objects.filter( active=True,
venue__contacts__account_verified=True,
date_expires__gte=date.today())
def location(self, obj):
return "/apply/{0}/".format(obj.pk)
# you can use reverse instead
def lastmod(self, obj):
return obj.date_posted

How to override the queryset giving the filters in list_filter?

Given the following models
class AnotherModel(models.Model):
n = models.IntegerField()
class MyModel(models.Model):
somefield = models.ForeignKey(AnotherModel)
and admin
class MyModelAdmin(admin.ModelAdmin):
list_filter = ('somefield',)
how can I filter the instances of AnotherModel to show only those with a given n value in my admin filter?
I need something like:
Filter
By somefield
all
[list of AnotherModel instances with given n]
See ModelAdmin.queryset and ModelAdmin.formfield_for_foreignkey. From the docs:
The queryset method on a ModelAdmin returns a QuerySet of all model instances that can be edited by the admin site. One use case for overriding this method is to show objects owned by the logged-in user:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
The formfield_for_foreignkey method on a ModelAdmin allows you to override the default formfield for a foreign keys field. For example, to return a subset of objects for this foreign key field based on the user:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This uses the HttpRequest instance to filter the Car foreign key field to only display the cars owned by the User instance.
[update]
Sorry, I failed to read the "filter" part. In Django >= 1.4 you can pass a subclass of django.contrib.admin.SimpleListFilter in the list_filter argument list, which you can use in order to override the lookups and queryset methods.
from datetime import date
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
class DecadeBornListFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('decade born')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'decade'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
return (
('80s', _('in the eighties')),
('90s', _('in the nineties')),
)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
# Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
if self.value() == '80s':
return queryset.filter(birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31))
if self.value() == '90s':
return queryset.filter(birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31))
class PersonAdmin(admin.ModelAdmin):
list_filter = (DecadeBornListFilter,)
Edit - this method has been pointed out to have issues, see below
You can do it like this:
Let's say you have a model called Animal, which has a ForeignKey field to a model called Species. In a particular admin list, you want to allow only certain species to be shown in the animals filter choices.
First, specify a custom ListFilter called SpeciesFilter in the Animal's ModelAdmin:
class AnimalAdmin(ModelAdmin):
list_filter = (('species', SpeciesFilter),)
Then define the SpeciesFilter:
from django.contrib.admin.filters import RelatedFieldListFilter
class SpeciesFilter(RelatedFieldListFilter):
def __init__(self, field, request, *args, **kwargs):
"""Get the species you want to limit it to.
This could be determined by the request,
But in this example we'll just specify an
arbitrary species"""
species = Species.objects.get(name='Tarantula')
#Limit the choices on the field
field.rel.limit_choices_to = {'species': species}
#Let the RelatedFieldListFilter do its magic
super(SpeciesFilter, self).__init__(field, request, *args, **kwargs)
That should do it.
I found another method similar to #seddonym, but doesn't mess with the caching. It is based on this Django code, but uses undocumented method field_choices, which can be subject to change in the future Django releases. The code for #seddonym's case would be:
from django.contrib.admin.filters import RelatedFieldListFilter
class SpeciesFilter(RelatedFieldListFilter):
def field_choices(self, field, request, model_admin):
return field.get_choices(include_blank=False, limit_choices_to={'name': 'Tarantula'})
Or in my case the working code is:
from django.contrib.admin.filters import RelatedFieldListFilter
class UnitFilter(RelatedFieldListFilter):
def field_choices(self, field, request, model_admin):
return field.get_choices(include_blank=False, limit_choices_to={'pk__in': request.user.administrated_units.all()})
I had to create my lookup fields from db table. I created custom filter class as below and displaying only related values to logged in user and filter accordingly:
class ShiftFilter_Org(admin.SimpleListFilter):
title = 'Organisation'
parameter_name = 'org'
def lookups(self, request, model_admin):
"""Get the organisations you want to limit"""
qs_org = Organisation.objects.filter(users=request.user)
list_org = []
for og in qs_org:
list_org.append(
(og.id, og.name)
)
return (
sorted(list_org, key=lambda tp:tp[1])
)
def queryset(self, request, queryset):
if self.value():
return queryset.filter(org=self.value())
For more visit Getting the most out of Django Admin filters
To use RelatedFieldListFilter like suggested in some answers, you need to pass a tuple to list_filter. Following the classes definition used in the original question, you would have:
class MyModelAdmin(admin.ModelAdmin):
class AnotherModelListFilter(admin.RelatedFieldListFilter):
def field_choices(self, field, request, model_admin):
ordering = self.field_admin_ordering(field, request, model_admin)
return field.get_choices(
include_blank=False,
ordering=ordering,
limit_choices_to=dict(n=<THE_VALUE_YOU_WANT>),
)
list_filter = (('somefield', AnotherModelListFilter),)