reverse django url to object, not view. possible? - django

I have a set of URLs for which I would like to retrieve the django model associated with this url, not the django view which is what the reverse URL Dispatcher does. The code would ideally look something like this:
urls_to_lookup = get_urls_to_lookup()
models = []
for url in urls_to_lookup:
model = retrieve_django_model(url)
models.append(model)
Since the urls I would like to lookup have unique models associated with them (via the #permalink decorator), it seems like this is possible but my google skillz are coming up empty handed. Thanks for your help!
EDIT In case it helps brainstorming solutions, I'm pulling these URLs from Google Analytics for all blog posts and I want to dynamically display most frequently viewed pages. The URL itself is helpful, but I would like to grab the title, teaser, etc for each blog post for display and that is all stored in the database.

If you are trying to create a sitemap; there's the sitemaps contrib app.
If you are trying to print out all the URLs in a nice format, see this answer.
I'm trying to think of a reason for having such a feature, but it escapes me. However, this should do what you want (not tested):
from django.db import models
def retrieve_django_model(url):
m_instances = [m for m in models.get_models() \
if m.objects.all().count()]
for m in m_instances:
if m.objects.all().order_by('?')[0].get_absolute_url() == url:
return m
else:
return None
Since we can only fetch the absolute url from instances not models, the initial list comprehension filters out those models for which there are no instances, and hence we cannot get the absolute URL.

Related

Add own functions in views.py wagtail

I have setup a wagtail website. It works great for postings like a blog and simply add new pages.
But what if I want to add some extra functions to a page. Like showing values from my own database in a table.
Normally i use a models.py, views.py and template.py. But now I don’t see any views.py to add functions or a urls.py to redirect to an url?
Don’t know where to start!
Or is this not the meaning of a wagtail site, to customize it that way?
Thnx in advanced.
You can certainly add additional data to pages. One option is to add the additional information to the context of a page type by overriding its get_context method. For example, this page is just a place to display a bunch of links. The links and the collections they belong to are plain old Django models (managed as snippets). And then there is a page model that queries the database like this:
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
collection_tuples = []
site = Site.find_for_request(request)
for collection in Collection.objects.filter(links__audiences=self.audience, site=site).distinct():
links = Link.objects.filter(audiences=self.audience, collections=collection, site=site)
collection_tuples.append((collection.name, links.order_by('text')))
# sort collection tuples by the collection name before sending to the template
context['collection_tuples'] = sorted(collection_tuples, key=lambda x: x[0], reverse=False)
return context
Another option is to do basically the same thing - but in a StructBlock. Then you can include the StructBlock in a StreamField on your page. Most of the Caltech site is written using blocks that can be included in one large StreamField on a page. Some of those blocks manage their own content, e.g. rich text blocks or image blocks, but others query data and render it in a block template.
To add to #cnk's excellent answer - you can absolutely use views.py and urls.py just as you would in an ordinary Django project. However, any views you define in that way will be available at a fixed URL, which means they'll be distinct from the Wagtail page system (where the URL for a page is determined by the page slug that the editor chooses within the Wagtail admin).
If you're defining URLs this way, make sure they appear above the include(wagtail_urls) route in your project's top-level urls.py.

Dynamic get_absolute_url using url query parameters

Big picture: I'd like my reverse method in get_absolute_url (see below) to return a url with a query parameter appended to it at the end, e.g. <url>?foo=bar. Further, I'd like bar to be specified by the POST request that triggered the call to get_absolute_url, either as an input to the form (but not a field represented by the model, something temporary) or as a url query parameter. I am easily able to access bar in my view using either method, but I can't seem to figure out how to access it in my model.
The motivation here is that my detail page splits up the fields from my model into different tabs using javascript (think https://www.w3schools.com/howto/howto_js_tabs.asp). When the user is updating the model, they choose which tab they want to update, and then the update template only renders the fields from the model which are related to that tab. More importantly, after the user submits the update, I want the detail page to know to open the specific tab that the user just edited.
(I understand how this works if the field is a part of the model; in get_absolute_url with parameters, the solution is pretty straightforward and involves using self.id. In my case though, bar is not a part of the model and I can't figure out how else to access it)
Some specifics: I have a model in my project called Context. I have implemented a generic DetailView and an update page for the model using a modelform called ContextForm and a generic UpdateView called ContextUpdate. Once the form is submitted, I redirect to the detail page using get_absolute_url in models.py:
def get_absolute_url(self):
return reverse("context:review",kwargs={"slug": self.slug})
My urlpatterns in urls.py looks something like:
urlpatterns = [
url(r'^(?P<slug>[-\w]+)$',views.ContextDetail.as_view(),name="review"),
url(r'^(?P<slug>[\w]+)/edit$',views.ContextUpdate.as_view(),name="edit"),
]
I am able to access this parameter in my UpdateView quite easily:
def post(self,request,**kwargs):
print (request.POST.get("bar")) #accessing input to form
print (request.GET.get("bar")) #accesssing url parameter
return super().post(request,**kwargs)
But when get_absolute_url is called inside the model, it seems I no longer have access to it.
Any suggestions for how to accomplish this? I want to use get_absolute_url (along with modelforms, generic views, etc.) so that I can follow Django conventions, but it seems like using get_absolute_url is making the functionality that I want difficult to accomplish. If the redirect to the detail view following the POST request were to happen inside my view, then I would know how to solve this (I think). Any thoughts or ideas would be greatly appreciated!
As you say, you can't access the request inside your get_absolute_url method. Therefore you should override get_success_url, from which you can access it.
def get_success_url(self):
return reverse(reverse("context:review", kwargs={"slug": self.object.slug}) + '?bar=%s' % self.request.GET.get('bar')
Or if you want to re-use get_absolute_url:
def get_success_url(self):
return self.object.get_absolute_url + '?bar=%s' % self.request.GET.get('bar')
The second option is DRYer but would break if get_absolute_url was changed to include a querystring like ?foo=foo.

Integrating Haystack in Django-CMS omitting Pages with View-Restrictions

I want to integrate haystack with django-cms making a search view. My CMS has pages with view restrictions (only a few authenticated users have access to some pages).
The problem is: when making a search, haystack gives me list with results from all pages, including pages to which the current user has not view permissions.
How can I integrate Haystack in a way that filters results, showing only the ones to which the current user has permissions for? If that's not possible, how to configure haystack letting it index only pages without view-restrictions? Any help is appreciated.
In my solution to this problem I use aldryn_search to do the integration of Haystack and django-cms. aldryn_search returns a listwith results from all pages, including the ones the current user hast not view-permissions for. To resolve this issue I'm inheriting from AldrynSearchView and override the get_queryset method like this:
def get_queryset(self):
queryset = super(IntranetSearchView, self).get_queryset()
for result in queryset.load_all():
page = result.object.page
# Begin: modified copy (queryset.exclude added) of cms.utils.decorators.cms_perms
if page:
if page.login_required and not self.request.user.is_authenticated():
queryset = queryset.exclude(id=result.id)
if not page.has_view_permission(self.request, user=self.request.user):
queryset = queryset.exclude(id=result.id)
# End: Copy
return queryset
using queryset.exclude() to exclude results the current user has not permissions for. After that I inherit from AldrynSearchApphook overriding the urls with my new View and than doing a apphoook_pool.register of the modified Apphook.

Django: How to set up a single view for multiple urls where each url points to subclass of a base model?

I have the following urls:
browse/college
browse/department
browse/subjects
I have Tag model where College, Department and Subject are all subclasses on Tag. All the urls will call a single view.
I want to create a view called browse_specfic_tag(request, model_name)
I was thinking of converting model name to model using get_model and do something like,
TagModel = get_model(model_name..)
but I am going to tie url name with model which might not be a good thing to do if I decided to rename either of them.
Any better way to do this?
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
…
The proper way of solving this is passing an extra option to the view. See related documentation entry.
Example:
url('^college/$', 'tag_view', {'model': College})
def tag_view(request, model):
records = model.objects.filter(…)
Furthermore, actions should not be included in URL's name. URLs should identify resources. Therefore I would skip the browse part of the URL.

Search multiple fields of django model without 3rd party app

I have a single model in my django app that I want to create a search form for. Is there a way to search all the fields in the model at once with the same search string? I've looked into xapian and solr but they seem like a lot of overhead for searching over 1 model. I want to be able to say something like:
results = Assignment.objects.filter(any_column = search_string)
I realize there might not be something that concise but right now the only option I can come up with other than using a search app is to check each field separately and concatenate the results together.
Once you have all the field names you can create Q objects using kwarg expansion and use reduce() along with operator.or_ to turn them into a single query.
qgroup = reduce(operator.or_, (Q(**{fieldname: value}) for fieldname in fieldnames))
asgns = Assignment.objects.filter(qgroup)
Old question, but for further reference I am adding this:
In django 1.10 SearchVector class was added.
Usage from the docs:
Searching against a single field is great but rather limiting. The Entry instances we’re searching belong to a Blog, which has a tagline field. To query against both fields, use a SearchVector:
>>> from django.contrib.postgres.search import SearchVector
>>> Entry.objects.annotate(
... search=SearchVector('body_text', 'blog__tagline'),
... ).filter(search='Cheese')
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]