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
Related
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.
views.py
def post_details(request,pk):
post = Post.objects.get(id=pk)
# next_post = Post.objects.filter(id=pk)
context={'post':post,'next':next_post}
return render(request, 'blog/post_detail.html', context)
blog-detail
<div class="s-content__pagenav group">
<div class="prev-nav">
<a href="#" rel="prev">
<span>Previous</span>
Tips on Minimalist Design
</a>
</div>
<div class="next-nav">
<a href="#" rel="next">
<span>Next</span>
Less Is More
</a>
</div>
</div>
models
# this is my model
class User(AbstractUser):
# pass
name = models.CharField(max_length=200)
bio = models.TextField(null=True)
email = models.EmailField(unique=True, null=True)
avatar = models.ImageField( null=True, upload_to='blog_media', default="images/avatar.svg")
facebook = models.URLField(blank=True, null=True)
twitter = models.URLField(blank=True, null=True)
dribbble = models.URLField(blank=True, null=True)
instagram = models.URLField(blank=True, null=True)
class Category(models.Model):
name = models.CharField(max_length=20)
class Meta:
verbose_name = 'Category'
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
title = models.CharField(max_length=200, blank=False);
description = models.TextField(null=True,blank=True)
image = models.ImageField(upload_to='blog_media')
url = models.URLField(null=True, blank=True)
body = HTMLField()
created = models.DateTimeField(auto_now=True)
updated = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Based on your comments, I'm assuming that you would like to get two related posts that have the same category as the current post.
If I'm correct, then one method you could use is to filter the queryset for the same category belonging to the current post then you could choose the next and previous posts of the current post from the retrieved queryset. For example:
def post_details(request, pk):
current_post = Post.objects.get(pk=pk) # retrieving the current post...
# filtering for related posts only by using the category of the current post
# using -> category_in=post.category.all() since it's a ManyToMany field
related_posts = Post.objects.filter(category_in=current_post.category.all())
# next -> get posts with id greater than the current post id, then get the first instance 'next post'
# previous -> get posts with id less than the current post id, then get the first instance 'previous post'
context = {
'post': current_post,
'next': related_posts.filter(id__gt=current_post.id).order_by('id').first(),
'previous': related_posts.filter(id__lt=current_post.id).order_by('-id').first()
}
return render(request, 'blog/post_detail.html', context)
Ideally, that should work.
A quick recommendation here as well... Instead of using Post.objects.get(pk=pk), I'd suggest using get_object_or_404() as this will handle any potential error that Post.objects.get(pk=pk) will throw. So a small update...
from django.shortcuts import get_object_or_404
def post_details(request, pk):
current_post = get_object_or_404(Post, pk=pk) # retrieving the current post...
# the rest of the code follows...
I have a page that basically displays user-entered data based on a project. The initial page contains just a handful of fields one being the project_name, which I have set as unique
project_name = models.CharField(max_length=50, blank=False, unique=True)
From this page, I want to open up another page based on the project_name which enables additional details to be added and stored in a different model.
Models.py
class Project(models.Model):
project_name = models.CharField(max_length=50, blank=False, unique=True)
project_website = models.URLField(max_length=50, blank=True)
project_description = models.TextField(blank=True)
ckeditor_classic = models.TextField(blank=True)
def __str__(self):
return str(self.project_name)
class Fundamentals(models.Model):
project_name = models.ForeignKey(Project, to_field='project_name', on_delete=models.CASCADE)
project_website = models.URLField(max_length=100, blank=True)
project_roadmap = models.CharField(max_length=25)
def __str__(self):
return str(self.project_name)
What I am struggling to do is load the second page but displaying the project_name and be able to associate the next set of data to the project
I think i need to do this via the views and urls but I can't get it to work.
View.py
#login_required
def AddFundamentals(request,project_id):
project = Project.objects.get(pk=project_id)
form = AddFundamentalsForm(request.POST or None, instance=project)
if form.is_valid():
form.save()
return redirect('dahsboard.html')
return render(request, 'pages/critical_fundementals.html', {'project': project, "form": form})
The error returned is
AddFundamentals() missing 1 required positional argument: 'project_id'
but I am passing this in via the URL
path('fundamentals/<project_id>', view=AddFundamentals, name="add_fundamentals"),
Do I need to do more in the view to return the data based on the PK? I'm really struggling with this :(
Update: Added Forms.PY and Link
class AddFundamentalsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AddFundamentalsForm, self).__init__(*args, **kwargs)
class Meta:
model = Fundamentals
fields = '__all__'
<a class="dropdown-item" href="{% url 'add_fundamentals project.id ' %}">Critical Fundamentals</a>
Thanks #Hills.
This resolved my issue path('fundamentals/<int:project_id>/'
I had to remove the trailing / and all sprung into life :)
Thanks everyone.
How I can get the pk of objects in home view, I try to make personal blog with posts and this posts include comments, I tried to get the post pk in order to get the comments that related to this post but I failed
Firstly I tried to get th pk by add it in def home_view(request, pk) but this template in home page www.mywebsite.com that's mean as I know, I cann't pass pk to the url so that's failed with me
My qustion is how I can get the Id of the post from home view?
My home View
post = []
for u in users:
p = Account.objects.get(username=u)
posts = p.post_set.all()
post.append(posts)
if len(video):
video = sorted(chain(*video), reverse=True, key=lambda video: video.created_date)
# here I tried to get the pk
comma = Post.objects.all()
our_post = Post.objects.get(pk=comma.pk)
comment = PostCommentIDF.objects.filter(post=our_post)
My Post Model
class Post(models.Model):
author = models.ForeignKey(Account, on_delete=models.CASCADE)
article = models.TextField(null=True, blank=True)
photo_article = models.ImageField(max_length=255, upload_to=get_poster_filepath)
created_date = models.DateTimeField(auto_now_add=True)
My Comment Model
class PostCommentIDF(MPTTModel):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='pos_com')
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='post_children')
author = models.ForeignKey(Account, on_delete=models.CASCADE)
content = models.TextField()
created_date = models.DateTimeField(auto_now_add=True)
status = models.BooleanField(default=True)
The Home View Url
path('', home_screen_view, name='home'),
Really not sure what's your goal by let me try it...
comma = Post.objects.all() returns QuerySet. It's something like list or array. Attribute pk is on each item in that 'array' not on array itself.
If you have QuerySet (list of your posts) you can loop over it:
comma = Post.objects.all() # returns all posts
for post in comma: # now post is single post
our_post = Post.objects.get(pk=post.pk) # now you can call 'pk' because post is single post and not QuerySet... this line is not needed because you already have post
comment = PostCommentIDF.objects.filter(post=post) # now get comments for that one post
EDIT:
How to use in view and template:
post_with_comments = []
comma = Post.objects.all() # returns all posts
for post in comma: # now post is single post
our_post = Post.objects.get(pk=post.pk) # now you can call 'pk' because post is single post and not QuerySet... this line is not needed because you already have post
comment = PostCommentIDF.objects.filter(post=post) # now get comments for that one post
post_with_comments.append({"post": post, "comments": comment})
context = {"items": post_with_comments}
return context
and then in the template you loop over all post_with_comments
{% for item in items %}
{{ item.post }}
{{ item.comments.count }}
{% endfor %}
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.