Django how the urls are resolved? - django

Im reviewing a sample DJango code and trying to understand how the urls are resolved?
list.html
Categories
{% for c in active_categories %}
{{c.name}}<br />
{% endfor %}
urls.py
from django.conf.urls import *
urlpatterns = patterns('ecomstore.catalog.views',
(r'^$','index',{'template_name':'catalog/index.html'},'catalog_home'),
(r'^category/(?P<category_slug>[-\w]+)/$','show_category',{'template_name':'catalog/category.html'},'catalog_category'),
(r'^product/(?P<product_slug>[-\w]+)/$','show_product',{'template_name':'catalog/product.html'},'catalog_product'),
)
The above html list all the categories without any problem and its called when I enter the following in the browser..[http:127.0.0.1:8000]
When I hover over - a href="{{p.get_absolute_url}} - I get the url resolved to--[http://127.0.0.1:8000/category/electronics/]
The p.get_absolute_url is resolved only to electronics but Im wondering how "category" is resolved in the url..
models.py
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50,unique=True,help_text='Unique value for product page URL created from name')
description = models.TextField()
is_active = models.BooleanField(default=True)
meta_keywords = models.CharField("Meta Keywords",max_length=255,help_text="comma-delimited set of SEO Keywords for meta tag")
meta_description = models.CharField("Meta description",max_length=255,help_text="Content for description meta tag")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'categories'
ordering = ['-created_at']
verbose_name_plural = 'Categories'
def __unicode__(self):
return self.name
#models.permalink
def get_absolute_url(self):
return ('catalog_category',(),{'category_slug':self.slug})
Hope my question is clear...

get_absolute_url is a function defined inside the model (for example, Category) model like this:
class Category(models.Model):
name = models.CharField(max_length=200)
...
def get_absolute_url(self):
return "/category/%s/" % self.slug
It is also possible to use reverse function to resolve the url using a pattern defined in urls.py:
def get_absolute_url(self):
return reverse('catalog_category', args=[str(self.slug)])
which is almost equal to an old-fashioned form:
#models.permalink
def get_absolute_url(self):
return ('catalog_category', (), {'category_slug': str(self.slug)})
In both ways, calling the get_absolute_url method on a Category object, for example Category(name="electronics"), will result in the string: /category/electronics/.
As you can see in your urls.py, the second url pattern is named catalog_category, which appeard in the reverse function argument. When you call the reverse function, Django will look into urls.py files, looking for a pattern named catalog_category, and the final url will be generated by replacing the url parameter (category_slug) with self.slug.

Related

Open listView with foreignkey from another listview with Django

I am working on a product overview page in Django.
For this I have three models: category, brand, product.
I have created a View with ListView of the category. I loop through them to display them. I then want to open another overview of all brands within that category.
How do I do this?
Here are my models:
class Category(models.Model):
category_name = models.CharField(max_length=200)
sub_category = models.CharField(max_length=200,blank=True,null=True)
category_picture = models.ImageField(upload_to='category/', null=True, blank=True)
def __str__(self):
if self.sub_category is None:
return self.category_name
else:
return f" {self.category_name} {self.sub_category}"
class Meta:
ordering = ['category_name']
class Brand(models.Model):
category = models.ForeignKey('Category', on_delete=models.SET_NULL,null=True,blank=True)
brand_name = models.CharField(max_length=200)
brand_owner = models.CharField(max_length=200)
brand_story = models.TextField()
brand_country = models.CharField(max_length=200)
def __str__(self):
return f"{self.brand_name}"
class Bottle(models.Model):
category_name = models.ForeignKey('Category', on_delete=models.SET_NULL,null=True,blank=True)
brand = models.ForeignKey('Brand', on_delete=models.CASCADE)
bottle_name = models.CharField(max_length=255)
bottle_info = models.TextField()
bottle_tasting_notes = models.TextField()
bottle_barcode = models.IntegerField()
bottle_image = models.ImageField(upload_to='bottles/',null=True)
def __str__(self):
return f"{self.brand.brand_name} {self.bottle_name}"
How do I open a listview of all brands within a certain category from a link of the category listview?
First thing is to create another listview that displays all brands within a specific category. We do this by creating a new get_queryset() method.
views.py
class BrandListView(ListView):
model = Brand
def get_queryset(self):
return Brand.objects.filter(category__category_name=self.kwargs['category'])
Next add a url to your URLS.py so it can be accessed. We're using category_name as part of the url so it's human readable
from .views import BrandListView
urlpatterns = [
path('brands/<str:category>/', PublisherBookList.as_view()), name= "brand_list"
]
Then, as you loop through your categories in your template, create a link to the url
{% for category in categories %}
{{category.category_name}} : See brands in category
{% endfor %}
This will work as long as your categories have fairly simple names. If not, you might want to use the ID in the URL instead, or add a slug field to the model and use that.

Add Title or Title Tag to url path

I'm hoping somebody can help me with this issue. Im having trouble adding my page/ article title to the url path. I've tried a number of ways can't seem to get it. If anyone could help that would be great.
My current Url path is "https://stackoverflow.com/article/1"
Would like it to be "https://stackoverflow.com/article/1/example-question-help", or some variation of that.
Below you can find how my views and url files are set up.
<a href="{% url 'article-detail' post.pk %}"
path('article/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),'
class ArticleDetailView(DetailView):
model = Post
template_name = 'article_detail.html'
def get_context_data(self, *args, **kwargs):
cat_menu = Category.objects.all()
stuff = get_object_or_404(Post, id=self.kwargs['pk'])
total_likes = stuff.total_likes()
liked = False
if stuff.likes.filter(id=self.request.user.id).exists():
liked = True
context = super(ArticleDetailView, self).get_context_data(*args, **kwargs)
context["cat_menu"] = cat_menu
context["total_likes"] = total_likes
context["liked"] = liked
return context
class Post(models.Model):
title = models.CharField(max_length=250)
header_image = models.ImageField(null=True, blank=True, upload_to="images/")
title_tag = models.CharField(max_length=250, default='none')
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
slug = models.SlugField(null=True)
# body = models.TextField()
post_date = models.DateField(auto_now_add=True)
category = models.CharField(max_length=250, default='')
snippet = models.CharField(max_length=250)
likes = models.ManyToManyField(User, related_name='blog_posts')
Assuming that your model has a slug field, called slug, which it looks like it may given your request, you'd change things like this;
path('article/<slug:slug>/', ArticleDetailView.as_view(), name='article-detail'),'
Django will then do the rest because SingleObjectMixin which is used by DetailView looks at the URL first for a primary key, then for a slug.
So this will give you URLs that look like;
https://stackoverflow.com/article/example-question-help
You can define a path that includes both the primary key and the slug:
path(
'article/<int:pk>/<slug:slug>/',
ArticleDetailView.as_view(),
name='article-detail',
),
This will automatically filter the item properly. In the link you then pass both the primary key and slug:
<a href="{% url 'article-detail' post.pk post.slug %}">
You can boost efficiency by determining the number of likes and whether the object is liked all in the same queryset with an Exists subquery [Django-doc]:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Exists, OuterRef
class ArticleDetailView(LoginRequiredMixin, DetailView):
model = Post
template_name = 'article_detail.html'
queryset = Post.objects.annotate(total_likes=Count('likes'))
def get_queryset(self, *args, **kwargs):
super().get_queryset(*args, **kwargs).annotate(
is_liked=Exists(
Post.likes.through.objects.filter(
post_id=OuterRef('pk'), user=request.user
)
)
)
def get_context_data(self, *args, **kwargs):
return super().get_context_data(
*args, **kwargs, cat_menu=Category.objects.all()
)
In the modeling, you might want to work with an AutoSlugField [readthedocs.io] from the django-autoslug package [readthedocs.io] to automatically slugify. Otherwise you will have to do this yourself. It also makes not much sense that the slug field is NULLable: normally a record will always have a slug. You thus might want to refactor the model to:
from autoslug import AutoSlugField
from django.conf import settings
class Post(models.Model):
title = models.CharField(max_length=250)
header_image = models.ImageField(null=True, blank=True, upload_to='images/')
title_tag = models.CharField(max_length=250, default='none')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
slug = models.AutoSlugField(populate_from='title')
post_date = models.DateField(auto_now_add=True)
category = models.CharField(max_length=250, default='')
snippet = models.CharField(max_length=250)
likes = models.ManyToManyField(
settings.AUTH_USER_MODEL, related_name='liked_posts'
)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Can't insert Title of Category in HTML with Django

I want to display only the title of my Django Category in HTML, but don't know how to access it properly. For the Category I use the MPTT library.
My views.py looks like this:
def category_products(request,id,slug):
products = Product.objects.filter(category_id=id)
category = Category.objects.all()
context={'products': products,
'category':category,
'slug': slug}
return render(request,'sondermuenz/category_products.html',context)
The model
class Category(MPTTModel):
title = models.CharField(max_length=200)
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
slug = models.SlugField(unique=True)
def __str__(self):
return self.title
class MPTTMeta:
order_insertion_by = ['title']
def __str__(self):
full_path = [self.title]
k = self.parent
while k is not None:
full_path.append(k.title)
k = k.parent
return ' -> '.join(full_path[::-1])
class Product(models.Model):
title = models.CharField(max_length=120)
description = models.TextField(blank=True,null=True)
image = models.ImageField(....)
...
The urls:
path('products_13/<int:id>/<slug:slug>', views.category_products, name='products'),
When I insert in my html file this
<h1>{{ slug }}</h1>
I can show the passed in slug, but how can I display the title of the product or category?
If I loop through it will show the same amount of titles as the looped objects, but I want to display it only once.
I hope someone can help. Thank you.
The {{ slug }} you are rendering is coming from the context in your view:
context={'products': products,
'category':category,
'slug': slug}
This is where you specify the names of objects that you want to render in the template. Right now, categories and products in your template will both be querysets, or a list of objects from the Django ORM. If you want to show just the first category title you could modify the context in the view:
context = {'first_category': Category.objects.first().name}
Then you could use first_category in the template. Or you can use Jinja's logic in the template to accomplish the same goal, without modifying context:
{{ category.first.name }}
You can preprocess objects when setting up your view's context and make them formatted the way you wish, or use Jinja in the template - both work and can be used in conjunction.
Solved. I added cat = Category.objects.get(pk=id) in views, now i can call cat.title in the html.

How to change url to show description instead of id with django?

I am building my first website. But instead of the page being products/15 (where 15 is the product id), I want it to show the description of the product. How can I achieve that?
Here are my models.py:
class Product(models.Model):
name = models.CharField(max_length=100)
category = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
views.py:
def product(request, id):
return render(request, 'product.html', {})
urls.py:
urlpatterns = [
path('products/<id>/', post, name='sub-detail'),
]
Thanks in advance for the help!
As babak gholamirad saids you should use a key that is UNIQUE in the model and the URL, just as id is but not description.
But, if you want to use and display a more "descriptive" KEY than the id in the URL: Django's slugs are made for that.
What is a "slug" in Django?
https://docs.djangoproject.com/en/3.0/ref/models/fields/#slugfield
models.py
class Product(models.Model):
name = models.CharField(max_length=100)
slug = models.CharField(max_length=256, unique=True)
category = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
views.py:
def product(request, slug):
product_object = Product.objects.get(slug=slug)
...
return render(request, 'product.html', {})
urls.py:
urlpatterns = [
path('products/<slug>/', post, name='sub-detail'),
]
usage:
https://<my-domain>/products/my-wonderfull-descriptive-product-name/

How django template url matching works

I'm trying to figure out how url matching works in django template.
What i'm trying to achieve is when clicking on a link it would bring up a specific objects.
Models.py
class PostManager(models.Manager):
def get_queryset(self):
return super(PostManager,self).get_queryset().filter(status='published')
class Post(models.Model):
STATUS_CHOICES = (('published','Published'),
('draft','Draft '))
FIELD_CHOICES = (('1','1 Title and body field'),
('2','2 Title and body fields'),
('3','3 Title and body fields'),
('4', '4 Title and body fields'),
('5', '5 Title and body fields'))
author = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='blog_post')
title = models.CharField(max_length=100)
sub_title = models.TextField(max_length=50,default="")
title_1 = models.CharField(max_length=100,null=True,blank=True)
title_1_body = models.TextField(null=True,blank=True)
title_2 = models.CharField(max_length=100,null=True,blank=True)
title_2_body = models.TextField(null=True,blank=True)
title_3 = models.CharField(max_length=100,null=True,blank=True)
title_3_body = models.TextField(null=True,blank=True)
title_4 = models.CharField(max_length=100,null=True,blank=True)
title_4_body = models.TextField(null=True,blank=True)
title_5 = models.CharField(max_length=100,null=True,blank=True)
title_5_body = models.TextField(null=True,blank=True)
created = models.DateField()
publish = models.DateTimeField(default=timezone.now)
slug = models.SlugField(max_length=250,
unique_for_date='created')
status = models.CharField(max_length=250,
choices=STATUS_CHOICES,
default='draft')
object = models.Manager()
postManager = PostManager()
class Meta():
ordering = ('publish',)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post_detail',
args=[self.slug])
def get_image_filename(instance, filename):
title = Post.title
slug = slugify(title)
return "media/%s-%s" % (slug, filename)
class Image(models.Model):
post = models.ForeignKey(Post,
on_delete=models.CASCADE,
related_name='images')
image_1 = models.ImageField(upload_to=get_image_filename,default='123.jpg',verbose_name="Image",null=True)
image_2 = models.ImageField(upload_to=get_image_filename,default='123.jpg',verbose_name="Image",null=True)
image_3 = models.ImageField(upload_to=get_image_filename,default='123.jpg',verbose_name="Image",null=True)
image_4 = models.ImageField(upload_to=get_image_filename,default='123.jpg',verbose_name="Image",null=True)
image_5 = models.ImageField(upload_to=get_image_filename,default='123.jpg',verbose_name="Image",null=True)
views.py
def post_detail(request):
post = get_object_or_404(Post)
return render(request, 'blog_application/templates/single.html',
{'post':post})
index.html
{% for elem in posts %}
<p class="mb-md-5">A small river named Duden flows by their place and supplies it with the necessary regelialia</p>
<p>Read More <span class="icon-arrow_forward ml-lg-4"></span></p>
{% endfor %}
urls.py
path('post_detail/<slug:slug>',views.post_detail,name='single'),
path('', views.show_index, name='index'),
I don't quite understand how url matching and linking works for templates. Could some one be kind enough to explain based on my example.
There's two issues in your view:
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog_application/templates/single.html',
{'post':post})
on a more general note, you receive the slug value from the url and pass it to your view with the argument.
Also, you need to pass the argument to get_object_or_404 see this.
before I dive, Let's agree that a slug is just a fancy name, It's just a string nothing else.
Ok so you want to understand how django urls work. back off plz
Let's first understand how URLs work, let's say I'll open this website www.example.com & just open it as is, The website just opens right? You expect the home page to be there
Now, let's open it as www.example.com?text=welcome
Press F12 in chrome & switch to networks tab, Essentially what happens is that you added a parameter which appears in chrome as 'query string parameter' at the very bottom of the networks tab, This says you wanted to visit this website with a parameter called text that contains the string hello
Ok, now what's the relation between this & the question? bear with me please
Imagine I have a social website with posts & I want to create a code "slug" for each post so that when It's typed as a parameter in the URL, It gets that specific post
now, this is your urlpatterns.py
path('post_detail/<slug:slug>', views.post_detail, name='single'),
path('', views.show_index, name='index'),
I'll assume that this is the project level urls.py for simplicity.
The user visits the website without any url params or subpages, it just calls your views.show_index
now the user visits your website/post_detail/, What happens is sees if there's any urlpattern in urlpatterns that matches this, but it doesn't, so it's just a 404
now the user visits your website/post_detail/ANY_RANDOM_TEXT
What happens is that there's actually a urlpattern that matches website/post_detail/ANY_RANDOM_TEXT
which path('post_detail/<slug:slug>', views.post_detail, name='single')
therefore, the "ANY_RANDOM_TEXT" must be the slug, Let's take it as the slug then!
now we have the slug, and the urlpattern will call views.post_detail (function or view) and pass the slug to it, so we must accept it right?
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog_application/templates/single.html',
{'post':post})
now you have access to the 'slug' which is just "ANY_RANDOM_TEXT" because we matched it, now get the post with slug that's the same as the slug in the url & just render it!
Btw don't forget the slash at the end of any urlpattern or otherwise the world is gonna explode