I'm trying to get something in Django, already for hours but without success. However I'm trying to use friendly urls like 'my-post-title-1234', where the number at the end is the post's id and the rest before that is the post's title. I got the url by using the slug and the id and I can retrive both in the view. So I check if ID exists and if it exists I do the rest, and if it doesn't exist I do 404 DoesNotExist. Inside the model I generated slug field and slugified title.
Everything works so far except one thing: User is able to write bla-bla-bla-1234 and it will still show him same data (since id exist). What I would like is following:
If user type in 'bla-bla-bla-1234' I would like to redirect him to correct slug 'my-post-title-1234'.
This is how my url looks like:
url(r'^(?P<slug>[-\w\d]+)-(?P<post_id>\d+)/$', views.post, name='post')
This is my model:
class Post(models.Model):
post_title = models.CharField(max_length = 125)
text = models.TextField()
slug = models.SlugField(null = False, blank = True)
def __str__(self):
return self.post_title
def save(self, *args, **kwargs):
self.slug = slugify(self.post_title)
super(Post, self).save(*args, **kwargs)
This is from my view:
def post(request, slug, post_id):
try:
post = Post.objects.get(id = post_id)
except Post.DoesNotExist:
raise Http404("Post does not exist")
return HttpResponse(post_id)
So the question is: how do I redirect(change url) to the correct slug from 'bla-bla-bla-1234' to 'my-post-title-1234', if user type in slug incorrectly while id is still good.
Thanks a lot.
Seems like you just need to check if the slug is correct, and if not do a redirect.
from django.shortcuts import get_object_or_404, redirect
def post(request, slug, post_id):
obj = get_object_or_404(Post, pk=post_id)
if obj.slug != slug:
return redirect('post', slug=obj.slug, post_id=obj.pk)
Note that there's a built-in shortcut for your first four lines: get_object_or_404. Also, be aware that calling an object within the function the same name as the function itself can lead to confusion; avoid doing that (which is why I've use obj above.)
In Django redirect comes with parameters, you can simply pass the slug in redirect by putting a ",".
return redirect("post",slug=slug)
Related
I understand that you can't directly use icontains on a foreign key when searching but I haven't found a solution yet.
Here is my search view in views.py (I have imported every model needed):
def search(request):
# if the user actually fills up the form
if request.method == "POST":
searched = request.POST['searched']
# author__icontains part is not working
posts = Post.objects.filter(Q(title__icontains=searched) | Q(author__author__icontains=searched))
return render(request, 'blog/search.html', {'searched': searched, 'posts': posts})
else:
return render(request, 'blog/search.html', {})
Here is my model in model.py:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
Mainly, this is not working:
posts = Post.objects.filter(Q(title__icontains=searched) | Q(author__author__icontains=searched))
The error is Related Field got invalid lookup: icontains
author is a User object. Therefore you should work with username, or first_name, or some other field. Likely author is also the value of a related_name=… [Django-doc] that thus makes a LEFT OUTER JOIN on another table, and thus would work on the primary key(s) of that table.
You thus filter with:
def search(request):
# if the user actually fills up the form
if request.method == 'POST':
searched = request.POST['searched']
# author__icontains part is not working
posts = Post.objects.filter(
Q(title__icontains=searched) |
Q(author__username__icontains=searched)
)
return render(request, 'blog/search.html', {'searched': searched, 'posts': posts})
return render(request, 'blog/search.html')
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.
Note: Searching is usually done through a GET rquest, since that means the query is stored in the querystring and thus the URL. This makes it convenient to for example share the URL with the query to someone else, or bookmark the result. POST requests are usually used for state-changing actions, or for requests with sensitive data.
This problem is caused by Django's inability to handle foreign keys with multiple values for a single field. The reason for this limitation is that Django doesn't know how to resolve these conflicts, so it simply ignores them. In your case, since there are two fields in your model that match the search criteria, Django will ignore both of those results and display an empty list.
To fix this issue, we need to add a new attribute to our model called "icontains" which would contain the value of the other field. Then, we'll set this attribute as a default value for the "author" field when querying from the database. Here is what your model should look like now:
class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField() date_posted = models.DateTimeField(default=timezone.now) author = models.ForeignKey(User, on_delete=models.CASCADE) icontains = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return self.title def get_absolute_url(self): return reverse('post-detail', kwargs=dict(pk=self.pk))
With this change, the code will work properly.
For more information about this limitation, see the Django documentation here: https://docs.djangoproject.com/en/1.9/topics/db/queries/#lookups-that-span-relationships
As title says I was trying to sort a list of posts using the django order_by method and since the field I used was later added to the list (The field was not created inside the model) it failed.
Is there anything I can do about it other than adding the field to the model which is something I really don't wanna do?
Here is the model code
class post(models.Model):
title = models.CharField(max_length=236)
content = models.TextField()
post_board = models.ForeignKey(board, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
release_date = models.DateField(auto_now_add=True)
views = models.IntegerField(default=0)
def __str__(self):
return self.title
The function that adds the extra field:
def forumdisplay(request, boardslug=None):
context = { 'board': None }
if boardslug:
context['board'] = board.objects.all().filter(slug=boardslug).first()
if context['board']:
context['posts'] = post.objects.all().filter(post_board=context['board'])
for eachpost in context['posts']:
eachpost.reply_count = len(reply.objects.all().filter(reply_to=eachpost))
eachpost.last_activity = eachpost.release_date
if eachpost.reply_count:
eachpost.last_activity = reply.objects.all().filter(reply_to=eachpost).order_by('release_date').first().release_date
context['posts'] = context['posts'].order_by('last_activity')
alter_posts(context['posts'])
else:
pass
return render(request, "board/forumdisplay.html", context)
The error I got:
Request Method: GET
Request URL: http://127.0.0.1:8000/forumdisplay/news/
Django Version: 3.0.4
Exception Type: FieldError
Exception Value:
Cannot resolve keyword 'last_activity' into field. Choices are: author, author_id, content, id, post_board, post_board_id, release_date, reply, title, views```
You can't.
order_by that you are trying to use is actually will be translated into SQL command to be executed on database, and while database has no column called last_activity so you can't apply this function to it.
what is the problem to add a new column to your DB and make it nullable?
this is the first time I had to ask something at StackOverflow, I'm both excited and scared, I don't know why.
I'm writing a Django app that just hosts web posts. The page is divided into three (3) categories (Index, lkmi and chiqung). Each post has a category (lkmi or chiqung).
On the INDEX page, you can see all the posts.
On the LKMI page, you can see only the lkmi posts.
On the CHIQUNG page, you can see only the chiqung posts.
All is controlled just by ONE VIEW called "index_page", which receives an argument called "cat" that is the URL from one of the categories (index, lkmi, chiqung). Based on that it dictates which posts to load.
* NOW THE PROBLEM *
I can't find why, but I'm only having trouble loading the lkmi section. The index page and the chiqung_page loads perfectly, but I'm having a
"Page Not Found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/lkmi/
Using the URLconf defined in blog.urls, Django tried these URL patterns, in this order:
admin/
<cat> [name='index_page']
post/<int:pk> [name='post_detallado']
^ckeditor/
The current path, lkmi/, didn't match any of these.
"
I'll leave here the models, views and urls.
Models
Category
class Category(models.Model):
name = models.CharField(max_length=50)
presentation = RichTextUploadingField(default='')
order = models.IntegerField(null=True)
url = models.CharField(max_length=50,default='',null=False,blank=True)
class Meta:
verbose_name = ("Categoria")
verbose_name_plural = ("Categorias")
def __str__(self):
return self.name
def get_postes(self):
print('Entre get_postes')
if self.url == '/':
return Post.objects.all().order_by('-published_date')
else:
return Post.objects.filter(category=self.pk).order_by('-published_date')
Post
class Post(models.Model):
title = models.CharField(('Titulo'),max_length=50)
category = models.ForeignKey("core.Category", verbose_name=("Categoria"), null=True, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now = True)
published_date = models.DateTimeField(auto_now=False, auto_now_add=False)
content = RichTextUploadingField()
author = models.CharField(("Autor/a"), max_length=50, default='El Autor')
class Meta:
verbose_name = ("Post")
verbose_name_plural = ("Postes")
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("_detail", kwargs={"pk": self.pk})
View That Handles The Categorization
def index_page(request, cat):
print('function index_page')
print('cat ='+str(cat))
category = get_object_or_404(Category,url=cat)
print('get_object_or_404(Category,url=cat). Executed')
print(str(category))
postes = category.get_postes()
print('category.get_postes(). Executed')
return render(request, 'index.html',{'postes': postes,
'category': category})
URL file from the app
urlpatterns = [
path('admin/',admin.site.urls),
path('<cat>', views.index_page, name='index_page'),
path('post/<int:pk>', views.detailed_post, name='detailed_post'),
]
Index Page
It loads
Chiqung Page
It loads
LKMI Page
It doesn't load...
If i use python manage.py shell and look for the category using Category.objects.get(url='lkmi') it returns the desired Category. Based on the comments of the view, it doesn't even enter the view function, because in the console it doesn't output "function index_page", even though it is the first line of the function.
Also, if i try the url "**127.0.0.1:8000/lkmi" it gives
Using the URLconf defined in blog.urls, Django tried these URL patterns, in this order:
1. admin/ -
2. <cat> name='index_page'] -
3. post/<int:pk> [name='post_detallado'].
But if i just enter "127.0.0.1:8000/whatever" the error is just
No Categoria matches the given query.
So in this case it entered the function and it gave the 404 error of not finding a matching Category. I still can't understand why it doens't execute the view when i try to enter to the lkmi url..
Thank you very much, I hope someone can help me with this so I cant continue going.
Your class is called "Category", but you call get_object_or_404() on "Categoria"
View should be:
def index_page(request, cat):
categoria = get_object_or_404(Category,url=cat)
postes = categoria.get_postes()
return render(request, 'index.html',{'postes': postes,
'categoria': categoria})
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.
In the Django Administrative Interface I'd like to Automatically Insert a logged in users username along with a blog post when the publish it, currently I have it displaying every user in a drop down to select from but obviously this is not great so I'd like it to automatically input this.
Here is my code:
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published_date = models.DateTimeField('date published')
author = models.ForeignKey(User, db_column="published_who")
def __unicode__(self):
return self.title
admin.py
from blog.models import Post
from django.contrib import admin
class PostAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
admin.site.register(Post, PostAdmin)
Many Thanks!
As I understand issue you need to exclude author from admin form:
class PostAdmin(admin.ModelAdmin):
exclude = ['author']
What you should use is in the Django docs: https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
You can overwrite the default behaviour of a ForeignKeyField in the admin with this.
Something along the lines of:
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == "author":
kwargs["initial"] = request.user
return super(PostAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
*This is untested
EDIT:
I didn't know whether you wanted to entirely disable the dropdown. With this method you wont. Instead you will have a default value of request.user but still be able to select another user.
If you want to make it a drop down with only one selection (weird behaviour :P) you could add:
kwargs["queryset"] = Post.objects.filter(author=request.user)