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
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.
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.
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.
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/
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