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.
Related
I am using django-tables2 and trying to hide url and rename it on field. for example, url link is www.youtube.com but on actual field, I want it to show as 'link' instead of revealing entire url link. how do I achieve this?
tables.py
class MyVideoTable(tables.Table):
class Meta:
model = PPVideo
fields = ('title', 'url')
models.py
class PPVideo
title = models.CharField('Title', max_length=100, null=True)
url = models.URLField('URL', max_length=150, null=True)
You can define a .render_url(…) method to specify how to render this column:
from django.utils.html import format_html
class MyVideoTable(tables.Table):
def render_url(self, value, record):
return format_html('link', value)
class Meta:
model = PPVideo
fields = ('title', 'url')
I saw in a video I didn't save that you could do something like this:
www.mysite.com/post/12345/this-is-a-title
Where the 12345 is the id and what actually is used to do the query and the this-is-a-tilte part is automatically assigned when loading the page and you can actually write whatever you want there and will still load the page.
How do I set this up in my path and do I still need a SlugField?
If you have a model with a title:
class Post(models.Model):
title = models.CharField(max_length=128)
# …
You can make a path that looks like:
urlpatterns = [
# …
path('post/<int:pk>/<slug:slug>/', post_detail, name='post_detail'),
]
the view can then fetch the corresponding Post object and slugify the title. If the slug does not match, it redirects it to the correct slug:
from django.shortcuts import get_object_or_404, redirect
from django.utils.text import slugify
def post_detail(request, pk, slug):
post = get_object_or_404(Post, pk=pk)
post_slug = sluglify(post.title)
if slug != post_slug:
# in case the slug does not match, redirect with the correct slug
return redirect('post_detail', pk=pk, slug=post_slug)
# … logic to render the object …
you'll need SlugField field and the slugify function to auto-generate the slug from the title.
try the code below
models.py:
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
class Post(models.Model):
"""A model holding common fields to Post model."""
slug = models.SlugField(_('slug'), max_length=255,
unique=True, null=True, blank=True,
help_text=_(
'If blank, the slug will be generated automatically '
'from the given title.'
)
)
title = models.CharField(_('title'), max_length=255,
unique=True,
help_text=_('The title of the post.')
)
[..]
def __str__(self):
return self.title
# Where the magic happens ..
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
urls.py:
path('post/<id:pk>/<slug:slug>/', views.post_detail, name='post_detail'),
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")
So I have been building a web app, and for the tags system, I decided to go with django-tagulous. However, when I import it in my models, I am getting this error:
AttributeError: module 'tagulous' has no attribute 'models' in Django
I have put it in my python INSTALLED_APP list in settings.py, and imported it in models.py, but still get the error.
Here is some code.
models.py
import tagulous
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=75)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
image = models.ImageField(upload_to='post_images',blank=True,null=True)
published_date = models.DateTimeField(blank=True,null=True,auto_now_add=True)
NSFW = models.BooleanField(default=False)
spoiler = models.BooleanField(default=False)
interests = tagulous.models.TagField()
tags = TaggableManager()
def __str__(self):
return self.title
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
In that Post class, I am using this on the interests one.
Here is my forms.py PostForm
class PostForm(forms.ModelForm):
class Meta():
model = Post
fields = ['title','text','image','interests','spoiler','NSFW']
widgets = {
'title':forms.TextInput(attrs={'class':'textinputclass'}),
'text':forms.Textarea(attrs={'class':'textareaclass editable'}),
}
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.fields['image'].required = False
So far I have found nothing online about this error. I was expecting it to work, but it didn't
Thanks :)
Well I continued to look deeper into their documentation and turns out they had a little known import option needed for the models.
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):
...