I am learning django by building a simple blogging app. While its all but done, I currently have individual posts having a url in the format https://my_site_dot_com/blog/entry/38/ where the number 38 corresponds to the primary key of said post.
What i want is it to have the format https://my_site_dot_com/blog/entry/this_is_custom_title/ where "this_is_custom_title" corresponds to the heading of the post. I have no idea how to accomplish this. Can anyone offer any assistance?
My model looks like:
class Entry(models.Model):
entry_title = models.CharField(max_length=50)
entry_text = models.TextField()
image = models.FileField(upload_to="media", blank=True)
entry_date = models.DateTimeField(auto_now_add=True)
entry_author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "blog"
def __str__(self):
return self.entry_title
I want the entry_title to the the custom url instead of the primary key.
My urls.py looks like this:
urlpatterns = [
path('', HomeView.as_view(), name="blog-home"),
path('entry/<int:pk>/', EntryView.as_view(), name="entry-detail"),
path('create_entry/', CreateEntryView.as_view(success_url='/'), name='create_entry'),
]
Edit:
The class handing the post looks like this:
class EntryView(DetailView):
model = Entry
template_name = 'blog/entry_detail.html'
data_set = random_info()
stuff_for_post = {
"info": data_set
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['rand_im'] = random_image()
context['tags'] = ['tag1','tag2','tag3']
return context
I'm an absolute noob in django and come from android/java. So please give an easy to understand explanation.
Thanks in advance
You may add a slug field to your Entry model and a get_absolute_url method. Don't forget to import reverse function from Django's url module.
from django.urls import reverse
class Entry(models.Model):
entry_title = models.CharField(max_length=50)
entry_text = models.TextField()
image = models.FileField(upload_to="media", blank=True)
entry_date = models.DateTimeField(auto_now_add=True)
entry_author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = models.SlugField()
def get_absolute_url(self):
return reverse('entry_detail', kwargs={'slug': self.slug})
class Meta:
verbose_name_plural = "blog"
def __str__(self):
return self.entry_title
Then, within the urls.py module of your app, add the following url pattern to the urlpatterns list. Don't forget to load the corresponding view, I guess it may be EntryView in this case.
from django.urls import path
from .views import EntryView
urlpatterns = [
...
path('<slug:slug>', EntryView.as_view(), name='entry_detail'), # new
...
]
Then the slug should replace the primary key pattern in the url.
To go a bit further, you can use a method within your model that slugify your title for instance. (define the method within the model then call it from the save method of the model, by overriding the save method)
https://docs.djangoproject.com/en/3.0/ref/utils/#django.utils.text.slugify
Currently you are passing an integer through your url. All you need to do is modify this slightly to pass a string through the url. Here is a similar question that discusses how to accomplish this.
As for changes you need to make in your code, urls.py will need to be updated
path('entry/<str:title>/', EntryView.as_view(), name="entry-detail")
You haven't provided your blog post view, but it will then look something like this:
def post(request, title):
template = "template.html"
post = Posts.objects.filter(entry_title==title)
return render(request, template, {'post':post})
If you are using a Class Based View you should use a slug.
First add a new field entry_slug to your Entry model and override the save method in order to automatically generate the entry_slug field:
class Entry(models.Model):
entry_title = models.CharField(max_length=50)
entry_slug = models.CharField(max_length=50)
...
def save(self, *args, **kwargs):
self.entry_slug = slugify(self.entry_title )
super(Entry, self).save(*args, **kwargs)
You can do by replacing the pk with entry_slug:
path('entry/<slug:entry_slug>/', EntryView.as_view(), name="entry-detail")
Related
models.py
class Notes(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
date_added = models.DateTimeField(default=timezone.now)
views.py
class HomepageView(ListView):
model = Notes
template_name = "home.html"
def get_queryset(self):
return Notes.objects.filter(Q(author=self.kwargs['pk']))
It points out pk has KeyError, is there any way to get rid of it
Thanks in advance...!
The self.kwargs contains the parameters, that are in the URL path. So in your urls.py, you should define shomething like:
# urls.py
from app.views import HomePageView
urlpatterns = [
path('<int:pk>/', HomePageView.as_view()),
]
Then you can visit the page, with a valid primary key, so for example /14 if 14 is a valid primary key for a User.
If you want to use the logged in user instead, you should use self.request.user instead:
# in case you want to use the logged in user
django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
class HomepageView(LoginRequired, ListView):
model = Notes
template_name = 'home.html'
def get_queryset(self):
return Notes.objects.filter(author=self.request.user)
Note: it might be better to use get_user_model [Django-doc] to point to the user model, since if you later change the user model, altering the foreign keys will be easier.
Note: You might want to consider using a DateTimeField(auto_now_add=True) instead of the models.DateTimeField(default=timezone.now), since that will make the field non-editable, etc.
By default, Django admin page basically form submitted data set to database naturally without any data edits.
However, I want to edit this.
When I develop blog to use Django admin, I have columns in table like this
| title | article_md | article_html
and I want to make article_html data not to edit directly in django admin page form but to generate html style data from markdown style data of article_md and to submit to database.
How can I do this? Is there any way to do something like generally controller in model.py or admin.py?
class Article(models.Model):
def __str__(self):
return str(self.title)
title = models.CharField(max_length=255)
article_md = models.TextField(null=True)
article_html = models.TextField(null=True)←I want it is generated by converting article_md's data
model.py
class Article(models.Model):
def __str__(self):
return str(self.title)
title = models.CharField(
max_length=255
)
article_md = models.TextField(
null=True
)
article_html = models.TextField(
null=True
)
def save(self, *args, **kwargs):
self.article_html = markdown.markdown(self.article_md, extensions=['gfm'])
super(Article, self).save(*args, **kwargs)
admin.py
class ArticleAdmin(admin.ModelAdmin):
exclude = ['article_html']
admin.site.register(Article, ArticleAdmin)
I'm making a hobby project with Django to store my ideas seperated by idea groups as the following:
class Idea(models.Model):
name = models.CharField(unique=True, max_length=50)
description = models.TextField()
in_process = models.BooleanField()
is_done = models.BooleanField()
group = models.ForeignKey(Group, on_delete=models.CASCADE, blank=False)
class Group(models.Model):
name = models.CharField(unique=True, max_length=25)
description = models.CharField(max_length=50, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=False)
Is there any way to restrict the currently logged in user from being able to see or modify ideas and idea groups created by other users using generic class based views?
class GroupDelete(LoginRequiredMixin, generic.DeleteView):
model = Group
pk_url_kwarg = "id"
success_url = reverse_lazy('ideas:list')
...and a url for example:
urlpatterns = [
path('<int:id>/delete', views.GroupDelete.as_view(), name='delete'),
]
I'm using Django 2.0.
I would suggest writing a custom mixin where you'd inherit the LoginRequiredMixin and then add your own logic verifying that the currently logged in user (which you can retreive from request.user) is the one who actually created the Group object.
Simple example would look something like this:
# mixins.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseNotFound, HttpResponseRedirect
class YourCustomMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
can_access = ... logic to check if user can access Group ...
disp = super().dispatch(request, *args, **kwargs)
if not isinstance(disp, HttpResponseRedirect) and not can_access:
return HttpResponseNotFound()
return disp
Once you have a value for the can_access flag, you call the LoginRequiredMixin's dispatch method and check if the result of that call is a redirect (to the login page) and check against the can_access flag, and then either return HttpResponseNotFound() or the original disp result.
Of course, you could also return HttpResponseForbidden() instead of HttpResponseNotFound().
You can then use it in your views, e.g.:
from your_app.mixins import YourCustomMixin
class GroupDelete(YourCustomMixin, generic.DeleteView):
...
I'm hoping this is just an issue of my poor regex understanding.
I'm attempting to use the exact code on Django 1.9's generic views to build a blog and personal site, and, down to the testing, here's where I run into trouble:
def test_post_page(self):
post = PostModelFactory()
first_post = Post.objects.all()[0]
post_url = first_post.get_absolute_url()
print(post_url)
response = self.client.get(post_url, follow=True)
self.assertEqual(response.status_code, 200)
So, through that print statement, I determined models.Post.get_absolute_url() was returning my homepage URL. Here's models.py:
class Post(models.Model):
title = models.CharField(max_length=200)
subtitle = models.CharField(max_length=200, default="")
pub_date = models.DateTimeField(auto_now_add=True)
text = models.TextField()
slug = models.SlugField(max_length=40,unique=True)
def get_absolute_url(self):
return "%s/" % (self.slug)
Should it come up, I copied down what the generic views documentation has, so my Detailview in /blog/urls.pyis as follows:
url(r'^(?P<slug>[-\w]+)/$', PostDetailView.as_view(), name='post-detail'),
Same of views.py:
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context
As far as I can tell, my get_absolute_url() function is simply not doing what I think it's doing, let alone what the regex in urls.py expects it to do.
Also: Is there anyone who can fully explain how slugfield works? I know it's a keyword generator to create a url, but I'm not sure how it works (or doesn't, as in this example).
Then, finally, in both a tutorial that I'm quasi-following alongside the documentation, and the documentation, itself, I'm not fully understanding where the variable names in templates are coming from (my understanding is that the request hit's the URL, which generates the data from views.py). The "ListView" object in the template shares the model name, "post" (or "article" in the documentation), where its pageview at the bottom is accessed simply through "page_obj", and the "DetailView" object is simply called "object". I also may be having a problem with paginating my ListView, ( which is identical to the documentation example, but with the extra line paginate_by = 2 right above get_context_data.
Thank you.
EDIT:
I've included PostModelFactory:
class PostModelFactory(DjangoModelFactory):
class Meta:
model = Post()
django_get_or_create = (
'title',
'subtitle',
'text',
'pub_date',
)
title = 'This is a test.'
subtitle = 'This is only a test.'
text = 'Madness? This is Sparta.'
pub_date = timezone.now()
def __init__(self):
self.save()
Edit: The issue turned out to be the lack of a slug in the PostModelFactory.
Ideally, you should use reverse in get_absolute_url, instead of hardcoding it.
from django.core.urlresolvers import reverse
class Post(models.Model):
...
def get_absolute_url(self):
return reverse('post-detail', args=[self.slug])
If you do hardcode the URL, it should contain a leading slash.
def get_absolute_url(self):
return "/%s/" % (self.slug)
If first_post.get_absolute_url is returning the homepage url with your current get_absolute_url, that suggests that the slug is an empty string.
I just started using Django for some time now and i stuck trying to work with slug. I know what they are, but it's dificult for me to define a simple slug and display it on my browser.
Here is the scenario: i've a models.py containing a class book with 3 fields: name, price and Autor. I want to retunr a slug string for name and autor with only hyphens (-) and lowercase letters.
My question is how to achieve this. Using only the model, the views and the html. This is what i've got till now. But don't know how to get it displayed on my browser(localhost:8000/book_name)
class Book(models.Model):
name = models.CharField(max_length=100)
price = models.IntegerField(default=0)
autor = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
#models.permalink
def get_absolute_url(self):
return 'app:book', (self.slug,)
from django.template.defaultfilters import slugify
Add the following to your model:
def save(self,*args,**kwargs):
self.slug = slugify(self.name)
return super(Book,self).save(*args,**kwargs)
Then you can use the slug in your urls:
url(r'mybooks/(?P<slug>[-\w]+)/$', views.BookDetailView.as_view(),name='book_details'),
Views:
from django.views import generic
class BookDetailView(generic.DetailView):
template_name = "templates/book_detail.html"
You may add this to your models.py file:
def save(self , *args , **kwargs):
if not self.slug:
self.slug=slugify(self.name)
super(Book, self).save(*args, **kwargs)
It worked for me.