How to generate urls with Django based on table variable - django

I have a blog where i would like to construct my URLs in following fashion: blah.com/blog/this-is-my-first-blog-post where this-is-my-first-blog-post is the title of one specific blog post.
Is there a way to generate these types of urls based on the title column in my table with blogposts?

You'll be better off storing the URL key as a SlugField. You can determine that value by using slugify.
Then your code for the url would be something like:
url(r'^(?P<slug>[-\w]+)/$','example_view'),
And you can use get_object_or_404:
def example_view(request, slug):
instance = get_object_or_404(Model, slug=slug)
Or you can use a DetailView CBV as shown in the docs.

There are a couple of options:
In django admin, you can make the field pre-populate on editing.
prepopulated_fields = {'slug': ('title',), }
This needs to be put in the admin.py file.
In case you want to do it outside of admin, you can use a pre-save signal and use slugify as mentioned above by #schillingt above.

Related

Django: from request.GET to QuerySet

I know how to create a html form with Django and I know how to access the values in request.GET.
I know how to use the ORM: MyModel.objects.filter(...)
Now I a missing how get from request.GET to a matching QuerySet
I could not find this in the Django docs.
Example:
class MyModel(models.Model):
name=models.CharField(max_length=1024)
if request.GET contains name=foo I would like to have a filter like MyModel.objects.filter(name__icontains=request.GET['name']).
I could code this on my own, but I think this is re-inventing the wheel. It feels too complicated to me.
Above is just one example. I prefer a solution which is more configuring than coding.
We can here construct a Q object:
from django.db.models import Q
MyModel.objects.filter(
Q([('{}__icontains'.format(k), v) for k, vs in request.GET.lists() for v in vs])
)
This will work as well given a certain key occurs multiple times in the querystring. Note that the fields you query over should support an __icontains [Django-doc] lookup.
Furthermore it might be unsafe to allow that, since for example a hacker could try to use user__password to make guesses on the (hashed) password of a user.
You should do it this way
filters = {}
for param, value in request.GET.items():
filters['{}_icontains'.format(param)] = value
queryset = MyModel.objects.filter(**filters)
Reference https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.QueryDict.items
You can use django-tables2 and django-filter
Explained here:
Django-filter can be used for generating interfaces similar to
the Django admin’s list_filter interface. It has an API very similar
to Django’s ModelForms. For example, if you had a Product model you
could have a filterset for it with the code:
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price', 'manufacturer']
And then in your view you could do:
def product_list(request):
filter = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'my_app/template.html', {'filter': filter})
How to use django-filter in django-tables2, see django-filter in django-table2

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.

New Urls patterns and multiple variables with the same name

I'm using Django 2.0 type of urls, and I have urls with multiple variables in them, with the same name. I'm using also ClassBasedView
path('/companies/<int:pk>/products/<int:pk>/', AccountCompanyProductDetailView.as_view()
I'm using pk because is the primary key and CBV will know how to use it (similar for other Model fields).
If I use other names, CBV will not know what to search.
In a CBV how can I get the parameters and know which is which. ?
How Django knows pk from which Model I need in each position ?
Django does not know how to handle this. You need to rename your parameters and access them in your CBV.
This could look like the following:
urls.py:
path('/companies/<int:pk1>/products/<int:pk2>/', AccountCompanyProductDetailView.as_view())
views.py:
class AccountCompanyProductDetailView(DetailView):
model = Product
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
company = get_object_or_404(Company, pk=pk1)
product = get_object_or_404(Product, pk=pk2)
return product
You would need to do this in other views too. Override the according methods like get_queryset. Access the kwargs as shown above.

Extending the Django change list view to include the object title it is related to

I am using the Django comments framework to allow commenting on articles in a blog. I want to display the title of the article that the comment(s) belongs to in the list view of the comments section where the comment name, content type, object id etc is.
How do I do this? I know you can hook up actions into your admin.py list view by writing a model method but in this case I do not have a model for comments since I am using the built in one.
Thanks
Somewhere in your code you can override the Comments ModelAdmin class and extend it to do what you want. This code isn't tested, but it should give you a general enough idea about how to customize the Comment admin:
from django.contrib import admin
from django.contrib.comments.admin import CommentsAdmin
class MyCommentsAdmin(CommentsAdmin):
# The callable that the list_display will call
def show_object_title(self):
return self.content_object.title
list_display = super(MyCommentsAdmin, self).list_display
list_display += ('show_object_title',)
admin.site.unregister(Comment)
admin.site.register(Comment, MyCommentsAdmin)
This doesn't work because the method gets two parameters and you have to use the second to get the title. This works:
def show_object_title(self, obj):
return obj.content_object.title
Also the super()-call doesn't work here. At least not the way described above. It may be easier to just copy and edit the list_display-tuple from the Comments-Admin-Source

Foreign keys in django admin list display

If a django model contains a foreign key field, and if that field is shown in list mode, then it shows up as text, instead of displaying a link to the foreign object.
Is it possible to automatically display all foreign keys as links instead of flat text?
(of course it is possible to do that on a field by field basis, but is there a general method?)
Example:
class Author(models.Model):
...
class Post(models.Model):
author = models.ForeignKey(Author)
Now I choose a ModelAdmin such that the author shows up in list mode:
class PostAdmin(admin.ModelAdmin):
list_display = [..., 'author',...]
Now in list mode, the author field will just use the __unicode__ method of the Author class to display the author. On the top of that I would like a link pointing to the url of the corresponding author in the admin site. Is that possible?
Manual method:
For the sake of completeness, I add the manual method. It would be to add a method author_link in the PostAdmin class:
def author_link(self, item):
return '%s' % (item.id, unicode(item))
author_link.allow_tags = True
That will work for that particular field but that is not what I want. I want a general method to achieve the same effect. (One of the problems is how to figure out automatically the path to an object in the django admin site.)
I was looking for a solution to the same problem and ran across this question... ended up solving it myself. The OP might not be interested anymore but this could still be useful to someone.
from functools import partial
from django.forms import MediaDefiningClass
class ModelAdminWithForeignKeyLinksMetaclass(MediaDefiningClass):
def __getattr__(cls, name):
def foreign_key_link(instance, field):
target = getattr(instance, field)
return u'%s' % (
target._meta.app_label, target._meta.module_name, target.id, unicode(target))
if name[:8] == 'link_to_':
method = partial(foreign_key_link, field=name[8:])
method.__name__ = name[8:]
method.allow_tags = True
setattr(cls, name, method)
return getattr(cls, name)
raise AttributeError
class Book(models.Model):
title = models.CharField()
author = models.ForeignKey(Author)
class BookAdmin(admin.ModelAdmin):
__metaclass__ = ModelAdminWithForeignKeyLinksMetaclass
list_display = ('title', 'link_to_author')
Replace 'partial' with Django's 'curry' if not using python >= 2.5.
I don't think there is a mechanism to do what you want automatically out of the box.
But as far as determining the path to an admin edit page based on the id of an object, all you need are two pieces of information:
a) self.model._meta.app_label
b) self.model._meta.module_name
Then, for instance, to go to the edit page for that model you would do:
'../%s_%s_change/%d' % (self.model._meta.app_label, self.model._meta.module_name, item.id)
Take a look at django.contrib.admin.options.ModelAdmin.get_urls to see how they do it.
I suppose you could have a callable that takes a model name and an id, creates a model of the specified type just to get the label and name (no need to hit the database) and generates the URL a above.
But are you sure you can't get by using inlines? It would make for a better user interface to have all the related components in one page...
Edit:
Inlines (linked to docs) allow an admin interface to display a parent-child relationship in one page instead of breaking it into two.
In the Post/Author example you provided, using inlines would mean that the page for editing Posts would also display an inline form for adding/editing/removing Authors. Much more natural to the end user.
What you can do in your admin list view is create a callable in the Post model that will render a comma separated list of Authors. So you will have your Post list view showing the proper Authors, and you edit the Authors associated to a Post directly in the Post admin interface.
See https://docs.djangoproject.com/en/stable/ref/contrib/admin/#admin-reverse-urls
Example:
from django.utils.html import format_html
def get_admin_change_link(app_label, model_name, obj_id, name):
url = reverse('admin:%s_%s_change' % (app_label, model_name),
args=(obj_id,))
return format_html('%s' % (
url, unicode(name)
))