Django sitemaps, two pages using the same object - django

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

Related

Disabling options in django-autocomplete-light

Just started using django-autocomplete-light (autocomplete.ModelSelect2) and while I have managed to get it working, I wondered if it is possible to pass disabled options?
I have a list of customers to choose from but some, for various reasons, shouldn't be selected they shouldn't be able to use them. I know I could filter these non-selectable customers out, but this wouldn't be very usable as the user might think that the customer isn't in the database. If so, could someone point me in the right direction as I'm not sure where to start.
It says in the Select2 documentation that disabling options should be possible. Presumably if I could also send a 'disabled':true within the returned json response that might do it.
OK, so here is what I came up with and it works.
view.py
The Select2ViewMixin is subclassed and then a 'disabled' attribute is added to the customer details. This value provided by the ParentAutocomplete view.
from dal import autocomplete
from dal_select2.views import Select2ViewMixin
from dal.views import BaseQuerySetView
class CustomSelect2ViewMixin(Select2ViewMixin):
def get_results(self, context):
return [
{
'id': self.get_result_value(result),
'text': self.get_result_label(result),
'selected_text': self.get_selected_result_label(result),
'disabled': self.is_disabled_choice(result), # <-- this gets added
} for result in context['object_list']
]
class CustomSelect2QuerySetView(CustomSelect2ViewMixin, BaseQuerySetView):
"""Adds ability to pass a disabled property to a choice."""
class ParentAutocomplete(CustomSelect2QuerySetView):
def get_queryset(self):
qs = Customer.objects.all()
if self.q:
qs = qs.filter(org_name__icontains=self.q)
return qs.order_by('org_name', 'org_city')
def get_result_label(self, item):
return item.selector_name
def get_selected_result_label(self, item):
return item.selector_name
def is_disabled_choice(self, item): # <-- this is where we determine if the record is selectable or not.
customer_id = self.forwarded.get('customer_id', None)
return not (item.can_have_children and not str(item.pk) == customer_id)
form.py
The form is then used as normal.
from dal import autocomplete
class CustomerBaseForm(forms.ModelForm):
customer_id= forms.IntegerField(required=False, widget=forms.HiddenInput)
class Meta:
model = Customer
widgets = {
'parent':autocomplete.ModelSelect2(
url='customer:parent-autocomplete',
forward=['customer_id'],
)
}
Hopefully this might be useful to someone.

Django: How to provide a category that changes at runtime in the urls.py?

I am trying to create a new URL level in my django-powered ecommerce site, meaning if a user is in domain.com/category/foo/ I am trying to add the next level down, in which they click on some element in foo and wind up in domain.com/category/foo/tag/bar/. To that end, I have created my urls.py line for detecting this, and I believe there is no problem here:
(r'^category/(?P<category_slug>[-\w]+)/tag/(?P<tag_slug>[-\w]+)/$', 'show_tag', {
'template_name':'catalog/product_list.html'},'catalog_tag'),
Once the request has been mapped through the urls.py, I know it is going to need some variables from views.py in order for get_adsolute_url to do its thing, so I create the view:
def show_tag(request,
tag_slug,
template_name="catalog/product_list.html"):
t = get_object_or_404(Tag,
slug=tag_slug)
products = t.product_set.all()
page_title = t.name
meta_keywords = t.meta_keywords
meta_description = t.meta_description
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
And lastly, in my Tag model, I set up my get_absolute_url to fill the keyword arguments:
#models.permalink
def get_absolute_url(self):
return ('catalog_tag', (), { 'category_slug': self.slug, 'tag_slug': self.slug })
I made sure the category and tag I'm about to request exist, and then I type domain.com/category/foo/tag/bar and I receive a
TypeError at /category/foo/tag/bar/
show_tag() got an unexpected keyword argument 'category_slug'
I believe I know where the error is, but I don't know how to solve it. my get_abolute_url sets 'category_slug': self.slug but that definition, as I said, lives in my Tag model. My Category model lives in the same models.py, but how do I tell my get_absolute_url to go find it?
Your view show_tag must have a parameter to accept the category_slug which is not there right now:
def show_tag(request,
category_slug, tag_slug,
template_name="catalog/product_list.html")

Django admin change_list view get ChangeList queryset - better solution than my monkey patch

I need to get a changelist view queryset in django admin. Currently, I have this monkey patch which makes 4 extra queries, so I'm looking for a better solution.
My point is: I want to pass some extra values to django admin change_list.html template which I get from creating queries. For those queries, I need the queryset which is used in django admin changelist view with request filters applied. This is the same data which I see filtered, ordered etc. I want to make graphs from this data.
Do you understand me? Thanks
#admin.py
from django.contrib.admin.views.main import ChangeList
class TicketAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
cl = ChangeList(request,
self.model,
self.list_display,
self.list_display_links,
self.list_filter,
self.date_hierarchy,
self.search_fields,
self.list_select_related,
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self) # 3 extra queries
filtered_query_set = cl.get_query_set(request) # 1 extra query
currencies_count = filtered_query_set.values('bookmaker__currency').distinct().count()
extra_context = {
'currencies_count': currencies_count,
}
return super(TicketAdmin, self).changelist_view(request, extra_context=extra_context)
I don't know if this answers to your question but the class ChangeList has an attribute called query_set (you can find the code here https://github.com/django/django/blob/master/django/contrib/admin/views/main.py) already containing the queryset.
BTW the changelist_view() function (source at https://github.com/django/django/blob/master/django/contrib/admin/options.py) returns a TemplateResponse (source at https://github.com/django/django/blob/master/django/template/response.py) that has a variable named context_data which points to the context. You can try to extend the content of this variable.
Below follows the untested code
class TicketAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
response = super(TicketAdmin, self).changelist_view(request, extra_context)
filtered_query_set = response.context_data["cl"].queryset
currencies_count = filtered_query_set.values('bookmaker__currency').distinct().count()
extra_context = {
'currencies_count': currencies_count,
}
response.context_data.update(extra_context)
return response

Special handling of multiple urls to one view with class based generic views

I am converting a WordPress site to a django one. I need to preserve the url structure for old posts, but have a different structure for new posts. I've done this by creating the 2 urls, setting a date in settings.py, then setting the absolute url like so:
urls.py
url(r'^reviews/archives/(?P<pk>\d+)$', PostDetail.as_view(), name="oldpost_view"),
posts/urls.py
url(r'^(?P<slug>[-\w]+)$', PostDetail.as_view(), name="post_view"),
posts/models.py
#property
def is_old_post(self):
wp_date = settings.WP_ARCHIVE_DATE
if self.post_date.date() < wp_date:
return True
# return False
#models.permalink
def get_abs_url(self):
if self.is_old_post:
return ('oldpost_view', (), {
'pk': self.id,
}
)
else:
return ('post_view', [str(self.url_slug)])
I am using one view for the 2 urls:
class PostDetail(DetailView):
model = Post
slug_field = 'url_slug'
template_name = "posts/detail.html"
This all works great. Now, what I need to is prevent new posts from being rendered by the oldpost_view url and vice versa. I know I can override the "get" and use reverse for this but how can I tell which url the request came from? What is the most efficient and DRY way to do this?
If you don't follow my advice with the '301' status code above, here is how I would do it:
Override the get method on the DetailView
If the date is before CUTOFF_DATE, and request.path[:10] != "reviews/arc" --> Redirect (301)
elseif date is after CUTOFF_DATE and request.path[:10] == "reviews/arc" --> redirect
Something roughly like that.
Based on Issac Kelly's feedback I was able to solve my problem. Here's the updated views:
class PostDetail(DetailView):
model = Post
slug_field = 'post_name'
template_name = "posts/detail.html"
def OldPostView(request, pk):
post_name = get_object_or_404(Post, pk=pk).post_name
return redirect('post_view', slug=post_name, permanent=True)
I also updated my models to utilize the "post_name" field that WordPress has, then simplified my permalink:
#models.permalink
def get_abs_url(self):
return ('post_view', [str(self.post_name)])
Thanks Issac!

Django: how to use custom manager in get_previous_by_FOO()?

I have a simple model MyModel with a date field named publication_date. I also have a custom manager that filters my model based on this date field.
This custom manager is accessible by .published and the default one by .objects.
from datetime import date, datetime
from django.db import models
class MyModelManager(models.Manager):
def get_query_set(self):
q = super(MyModelManager, self).get_query_set()
return q.filter(publication_date__lte=datetime.now())
class MyModel(models.Model):
...
publication_date = models.DateField(default=date.today())
objects = models.Manager()
published = MyModelManager()
This way, I got access to all objects in the admin but only to published ones in my views (using MyModel.published.all() queryset).
I also have
def get_previous(self):
return self.get_previous_by_publication_date()
def get_next(self):
return self.get_next_by_publication_date()
which I use in my templates: when viewing an object I can link to the previous and next object using
{{ object.get_previous }}
The problem is: this returns the previous object in the default queryset (objects) and not in my custom one (published).
I wonder how I can do to tell to this basic model functions (get_previous_by_FOO) to use my custom manager.
Or, if it's not possible, how to do the same thing with another solution.
Thanks in advance for any advice.
Edit
The view is called this way in my urlconf, using object_detail from the generic views.
(r'^(?P<slug>[\w-]+)$', object_detail,
{
'queryset': MyModel.published.all(),
'slug_field': 'slug',
},
'mymodel-detail'
),
I'm using Django 1.2.
In fact, get_next_or_previous_by_FIELD() Django function (which is used by get_previous_by_publication_date...) uses the default_manager.
So I have adapted it to reimplement my own utility function
def _own_get_next_or_previous_by_FIELD(self, field, is_next):
if not self.pk:
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = is_next and 'gt' or 'lt'
order = not is_next and '-' or ''
param = smart_str(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = MyModel.published.filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
except IndexError:
def get_previous(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], False)
def get_next(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], True)
This is not a very clean solution, as I need to hardcode the queryset and the field used, but at least it works.