change my url from depending on id to title | django - django

I've a url for checking books by its id:
path('book/<int:book_id>', views.book, name='book'),
view:
def book(request, book_id):
book = get_object_or_404(Book, pk=book_id)
context = {
'book': book
}
return render(request, 'media/book.html', context)
but my client asked me to change it for the title instead but I tried it and it didn't seem to work, there are no examples for it in the docs either.

The NOTE in the answer above does not take SEO into account. For SEO purposes, it is much better to have a url that includes the name of the book rather than just the ID. If you are already in production, then remember to do a permanent redirect in your view from all ID-based urls to the slug-based url. Your Book model definition (or Product or whatever you've called it) should include a slugified field:
class Book(models.Model):
name = models.CharField(...)
slug = models.CharField(max_length=255, default='', unique=True, blank=True)
other_fields...
def save(self, *args, **kwargs):
# Create slug for SEO
#Don't ever change once established since is part of the URI
if self.slug is None or self.slug == '':
self.slug = slugify(self.name)
super(Book, self).save(*args, **kwargs)

You need to change the url to 'book/<str:book_title>', and also adjust your function accordingly:
def book(request, book_title):
book = get_object_or_404(Book, yourfieldfortitle=book_title)
...
It might be helpful to try accessing the url first with a pattern that you know must work. NOTE: this makes the strong assumption that a book is identified by its title, otherwise using the primary key is the proper way even if it doesn't "look nice" as a url.

Related

Dynamic URLs for hierarchy tree on Django

I would like to build a hierarchy tree of content with no depth limits.
models.py:
class Element(models.Model):
name = models.CharField(verbose_name="Nombre", max_length=250)
parent = models.ForeignKey("self", on_delete=models.CASCADE)
slug = models.TextField(verbose_name="Slug", blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Element, self).save(*args, **kwargs)
.../element_n-1/element_n/element_n+1/...
How do I have to write the path in urls.py to get this functionality?
You can use path: type converter for your path() definition:
urlconf.py:
path('prefix/<path:path>', my_view),
More info: https://docs.djangoproject.com/en/3.1/topics/http/urls/#path-converters
views.py:
def my_view(request, path):
elements_list = path.split('/')
...
But as it was said in the previous answer, there's a limit for URL length so your path cannot include arbitrary number of elements.
Such URL is not possible to create in any web framework, because URLs have length limits (2000 characters long), so no web framework will allow a URL to accept growing dynamically based on a Model Hierarchy inside of it.
You need to think of another way to let your app deliver such functionality, in a different way other than URLs.

matching django url with views using a model slug

my problem is that I can not establish a reverse match, most likely doing something wrong with my url definition (?). Ultimately what I am trying to do is the following:
User selects 2 location points which the 'new_pointview' view, saves into a DB. I also define a unique slug which contains location information and save it to the DB via the save() within the model. Then the user should be redirected to a url (pointview view) which uses the slug I created in the previous step i.e /pointview/slug. Here is the code:
models.py
class Points(models.Model):
starting_point_longitude = models.FloatField(null=True)
starting_point_latitude = models.FloatField(null=True)
ending_point_longitude = models.FloatField(null=True)
ending_point_latitude = models.FloatField(null=True)
url = models.SlugField(max_length=250, null=True, unique=True, blank=False)
def save(self, *args, **kwargs):
self.url = 'start_lon-{0}-start_lat-{1}-end_lon-{2}-end_lat-' \
'{3}'.format(self.starting_point_longitude,
self.starting_point_latitude,
self.ending_point_longitude,
self.ending_point_latitude)
super(Points, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('pointview', kwargs={'url': self.url})
views.py
def pointview(request, url):
point = get_object_or_404(Points, url=url)
content = {'starting_point_longitude':
point.starting_point_longitude,
'starting_point_latitude':
point.starting_point_latitude,
'ending_point_longitude':
point.ending_point_longitude,
'ending_point_latitude':
point.ending_point_latitude}
return render(request, 'points.html', {'user_bundle': content})
def new_pointview(request):
Points.objects.create(
starting_point_longitude=request.POST['starting_point_longitude'],
starting_point_latitude=request.POST['starting_point_latitude'],
ending_point_longitude=request.POST['ending_point_longitude'],
ending_point_latitude=request.POST['ending_point_latitude'],
)
points_filtered = Points.objects.filter(
starting_point_longitude=request.POST[
'starting_point_longitude']).filter(
starting_point_latitude=request.POST[
'starting_point_latitude'])
unique_url = points_filtered.values()[0]['url']
return redirect('/pointview/{0}/'.format(unique_url))
urls.py
urlpatterns = [
path(r'^pointview/(?P<url>[-\w]+)/$', views.pointview, name='pointview'),
path('^new_pointview/', views.new_pointview, name='new_pointview'),
]
The error:
The current path, pointview/start_lon-738949.9146592747-start_lat--153698.8751025315-end_lon-759997.8063993475-end_lat--168467.65638300427/, didn't match any of URL patterns. Hope you can give me some feedback here..
For future reference, it was a regex problem, using the following non intuitive regex solved it:
url('^pointview/(?P<url>start_lon[--W]+start_lat[--W]+end_lon[--W]+end_lat[--W]+)/', views.pointview, name='pointview')
I would be very interesting though if someone can give a more elegant solution.

"Next" and "Previous" button on Django, on posts with Slugs

What I want is to create "Next Post" and "Previous Post" on Django. What I have managed to do until now is to link the button to a page where the primary key is one above or one below the current post:
<a class="btn btn-default" href="{% url 'post_detail' slug=post.slug pk=post.pk|add:'1' %}">Next</a>
There are two issues: one is with the Slug and with the URL, and the other is with the showing (or hiding) of the button.
When I click next, I am taken to the next post (if it exists. If it doens't I receive an error telling me that such a page doesn't exist). The URL remains the same as the previous post, correspondingly: If I click it three times, the URL displayed will be the one of the second post. What I would like is to somehow also display the corresponding slug.
The second problem, is that I want to disable the button if there is no post before or after. But the following piece of code:
{% if post.pk|add:'1' %}
Which is what I have come up with up until now. But apparently it checks if such a number exists, not if the post exists or not.
The view class for this page is the following:
class PostDetailView(HitCountDetailView):
model = Post
slug_field = "title"
count_hit = True
Whereas the model is (removing irrelevant-to-this-issue code):
#python_2_unicode_compatible
class Post(models.Model, HitCountMixin):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=100, null=False, blank=False)
text = models.TextField(null=False, blank=False)
slug = models.SlugField(max_length=100, blank=True)
# rewrite save method
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
def article_pre_save(signal, instance, sender, **kwargs):
instance.slug = slugify(instance.title)
signals.pre_save.connect(article_pre_save, sender="blog.Post")
# go to post page
def get_absolute_url(self):
return reverse("post_detail", kwargs={'slug':self.slug, 'pk':self.pk})
# retorna o título do post
def __str__(self):
return self.title
And the related url:
url(_(r'^post/(?P<pk>\d+)-(?P<slug>[-\w]+)/$'), views.PostDetailView.as_view(), name='post_detail'),
How could I proceed to this? I thought about writing a Raw SQL query (I've been having problems with this too, but that's another matter), but I don't know, up to now, how I could actually link it to the template. Thank you for your help, if you will, and attention, in advance.
django-next-prev refer this. its good package for these kinds of requirements. this package will get you the next and previous object of the current object. after that, you have to build the next and previous URL using reverse or reverze_lazy methods in Django

Outputting data from a django app

This is probably something really simple but for some reason I just can't seem to do it.
I'm trying to output some data from a blog (app) that I have created. The blog is working fine and out putting the content in the model fields that I have created and outputting to the templates I have specified.
But when tryingn to output the information out to the homepage nothing is showing. I'm fairly new to django and I think I might be missing something.
Do I need to include something to pages that are outside of the app? or do I need to set up somethin in the urls file?
I hope this makes sense as I don't think it's anything to complicated but I just think I'm missing something!
Thanks.
CODE:
url(r'blog/(?P<slug>[-\w]+)/$', 'blog.views.blog', name="blog"),
url(r'blog/', 'blog.views.blog_index', name="blog_index"),
def blog_index(request):
blogs = Blog.objects.filter(active=True)
return render_to_response('blog/index.html', {
'blogs':blogs,
}, context_instance=RequestContext(request))
def blog(request, slug):
blog = get_object_or_404(Blog, active=True, slug=slug)
return render_to_response('blog/blog_post.html', {
'blog': blog
}, context_instance=RequestContext(request))
class Blog(TimeStampedActivate):
title = models.CharField(max_length=255, help_text="Can be anything up to 255 character")
slug = models.SlugField()
description = models.TextField(blank=True, help_text="Give a short description of the news post")
content = models.TextField(blank=True, help_text="This is the main content for the news post")
user = models.ForeignKey(User, related_name="blog")
def __unicode__(self):
return self.title
#models.permalink
def get_absolute_url(self):
return ('blog', (), {
'slug': self.slug
})
Are you saying that going to mysite.com/blog/ is displaying correctly, but you also want it to be the sites index page at mysite.com?
If so, you need to add a new pattern to your projects urls.py file, like so:
url(r'^$', 'blog.views.blog_index')
Doing this will make mysite.com/blog/ and mysite.com route to the same view.

Django filebrowser, model specific directory parameter for FileBrowserField

Using django-filebrowser and the FileBrowserField I am attempting to assign the directory parameter based off a key in the model itself. So using a blog example, if I were saving photos for different posts, the photos would be in directories named after the blog id. So if the MEDIA_ROOT was/some/path/to/media/filebrowser_files/ I wish to dynamically assign the directory paramter to be MEDIA_ROOT+str(model_pk)
At this point I have attempted to do something resembling the following, but do not understand how to obtain the id of the current object. (DoesNotExist exception with "No exception supplied") I know the error exists within the attempt to use self.page, but I do not know how to do this correctly. Could someone provide some insight as to where my logic is flawed and what I can do to fix it? Thanks much.
class PageImage(models.Model):
page = models.ForeignKey(Page)
page_photo = FileBrowseField("Image", max_length=200, blank=True, null=True)
def __init__(self, *args, **kwargs):
super(PageImage, self).__init__(*args, **kwargs)
path = settings.MEDIA_ROOT+'pages/unfiled/'
if self.page:
path = settings.MEDIA_ROOT+'pages/'+str(self.page)+'/'
if not os.path.exists(path):
os.makedirs(path)
self._meta.get_field_by_name("page_photo")[0].directory = path
I realize I didn't look closer at your code. self.page will not work because you're initializing the Model instance and self.page has not been set to a page in the database. You should try instead:
class PageImage(models.Model):
page = models.ForeignKey(Page)
page_photo = FileBrowseField("Image", max_length=200, blank=True, null=True)
def save(self, *args, **kwargs):
path = settings.MEDIA_ROOT+'pages/unfiled/'
if self.page:
path = settings.MEDIA_ROOT+'pages/'+str(self.page)+'/'
if not os.path.exists(path):
os.makedirs(path)
self._meta.get_field_by_name("page_photo")[0].directory = path
super(PageImage, self).save(*args, **kwargs)
doing what you want only after you've assigned a Page to the field in PageImage.