Django and models / views, AttributeError - django

I am stuck with my miniportfolio in django, not sure what the error is telling me.
models.py
class Page(models.Model):
title = models.CharField(max_length=30)
slug = models.SlugField()
content = models.TextField()
def get_absolute_url(self):
return "/%s" % self.slug
def __unicode__(self):
return self.title
def __repr__(self):
return self.name
views.py
def page_view(request, **kwargs):
slug = kwargs.get('slug')
page = get_object_or_404(Page, slug=slug)
content = page.content
title = page.title
return render_to_response("base.html", {"content":content, "title":title})
urls.py
url(r'^(?P<page>)/$', page_view),
I get some really weird urls when I create a new page from the admin interface.
127.0.0.1:3020/admin/r/7/3/ <-- I do not know why they are like this. Tips?
When I visit the pages I've created I get a 404.

change:
def get_absolute_url(self):
return ('page', (), {
'slug': self.slug,
'id': self.id,
})
to something like:
def get_absolute_url(self):
return "/APP_URL_PATH/%s/" % self.slug # or use id, there's no need for two unique identifiers
and
def my_view(request, slug, id):
page = get_object_or_404(pk=id)
to something like:
def my_view(request, **kwargs):
slug = kwargs.get('slug')
page = get_object_or_404(Page, slug=slug)
Your indention is probably bad because of copy pasting, you might want to edit your post. Have a look at django's documentation for an idea on url capture groups

def page_view(request, **kwargs):
slug = kwargs.get('page') # hello page! I found you
...
because you use page for variable not slug in urls.py
url(r'^(?P<page>)/$', page_view),
Or you can write view like
def page_view(request, page=None):
page_from_db = get_object_or_404(Page, slug=page)
return render(request, "base.html", {"page": page_from_db})
in tpl
{{ page.content }}
And if you use 1.4 use from django.shortcuts import render
render automatically use context

Related

Generic DeleteView is returning django.db.models.query_utils.DeferredAttribute object at 0x04725628 - Django

Disclaimer: I'm just a novice trying to learn Django
Hello, I'm trying to refactor my code and modify all the views that I have created to be Class Based Views.
I have an issue loading a form with DeleteView that is showing the data and at the same time is disabled.
I have some success and the only thing that I cannot figure out how to do is to show the data instead of the message that appears now "<django.db.models.query_utils.DeferredAttribute object at 0x04725628>"
+models.py:
class Note(models.Model):
title = models.CharField(max_length=30)
image_url = models.URLField()
content = models.TextField()
owner = models.ForeignKey(Profile, default=8, on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse(self.pk)
def __str__(self):
return f'{self.title}'
+forms.py
class NoteForm(forms.ModelForm):
class Meta:
model = Note
exclude = ('owner',)
class DeleteNoteForm(NoteForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (_, field) in self.fields.items():
field.widget.attrs['readonly'] = True
field.widget.attrs['disabled'] = True
+views.py
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.model)
return data
+urls.py
path('delete/<int:pk>/', views.DeleteNoteView.as_view(), name='delete note'),
+template
<!--note delete data form-->
<div class="form">
<form method="POST">
{{ form }}
{% csrf_token %}
<input type="submit" value="Delete"/>
</form>
</div>
<!--end note delete data form-->
If I use my view it works fine, but I want to modify it.
def delete_note(request, pk):
note = Note.objects.get(pk=pk)
if request.method=='GET':
note_form = DeleteNoteForm(instance=note)
context = {
'note_form': note_form
}
return render(request, 'note-delete.html', context)
else:
note.delete()
return redirect('home page')
Could someone tell me where I'm wrong and how I can fix it or at least provide me a link with information to understand why this is happening?
You are passing a reference to the model class in your DeleteNoteView, whereas you should use the object that is removed, so:
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
# user self.object instead of self.model &downarrow;
data['form'] = self.form_class(instance=self.object)
return data
I would also advise to filter the QuerySet such that it is impossible for another user (a user that is not the owner of a Note to remove that Note:
from django.contrib.auth.mixins import LoginRequiredMixin
class DeleteNoteView(LoginRequiredMixin, DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
owner=self.request.user
)
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.object)
return data

One view for two models with redirect

I have Django app with the following model:
class Author(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
This is now using simple generic view:
class AuthorDetail(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# some additional context thingies
return context
and with the following URL configuration:
path('author/<slug:slug>/', AuthorDetail.as_view(), name='author-detail'),
Now I want to introduce simple aliases for authors, so for example instead of /author/william-shakespeare I can reach the page also as /author/ws or /author/my-favourite-author.
I came up with this idea (I know that destination could be key to Author, but that (I think) would not change much in the case):
class AuthorAlias(models.Model):
slug = models.SlugField(max_length=200, unique=True)
destination = models.CharField(max_length=200)
So to achieve the redirect I came up with the following in the view:
def get(self, request, **kwargs):
slug = kwargs['slug']
try:
self.object = self.get_object()
except Http404:
self.object = get_object_or_404(AuthorAlias, slug=slug)
return redirect(reverse('author-detail', args=[self.object.destination]))
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
Everything seems to be working fine, but I was wondering it there is a better approach and if this approach can cause any issues I'm not seeing?
The problem here is that destination does not per se refer to a real author. You can alter this by using a ForeignKey instead:
class AuthorAlias(models.Model):
destination = models.ForeignKey('Author', on_delete=models.CASCADE)>
slug = models.SlugField(max_length=200, unique=True)
In the Author class it might be better to implement the get_absolute_url(…) method [Django-doc] to define a canonical url:
from django.urls import reverse
class Author(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'slug': self.slug})
Now we can implement the DetailView with:
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
class AuthorDetail(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# some additional context thingies
return context
def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except Http404:
return redirect(get_object_or_404(
Author,
authoralias__slug=self.kwargs['slug']
))
context = self.get_context_data(object=self.object)
return self.render_to_response(context)

error with slug in django - template it show all posts data in each post

Error with a slug in Django - template it shows all posts data in each post
when I create a new post and write my data it shows all data from other posts why is that?
and how I can fix it?
also how I can add an auto-generation slug?
models.py :
from django.urls import reverse
from django.utils.text import slugify
class Android(models.Model):
title = models.CharField(max_length=50,default="",help_text="this is title for slug not post!")
name = models.CharField(max_length=50,default="")
app_contect = models.CharField(max_length=240,default="")
app_image = models.ImageField(upload_to='images/',null=True, blank=True)
post_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
post_tag = models.CharField(max_length=50,default="",choices = BLOG_SECTION_CHOICES)
slug = models.SlugField(null=True,uniqe=True) # new
def get_absolute_url(self):
return reverse('android_posts', kwargs={'slug': self.slug}) # new
def get_image(self):
if self.app_image and hasattr(self.app_image, 'url'):
return self.app_image.url
else:
return '/path/to/default/image'
def __str__(self):
return self.name
class Meta:
ordering = ('-post_date',)
views.py :
def android_posts(request,slug):
android_posts = Android.objects.all()
context = {'android_posts':android_posts}
return render(request,'android/android_problems_fix.html', { 'android_posts': android_posts })
html page :
{% for android in android_posts %}
<h1 id="font_control_for_header_in_all_pages">{{android.name}}</h1>
<hr>
<p id="font_control_for_all_pages">{{android.app_contect}}</p>
{% endfor %}
url :
path('Android/<slug:slug>', views.android_posts, name='android_posts'),
To autogenerate your slug (and only do it on initial save, so that it remains consistent), add the generation to your model save method:
def save(self, *args, **kwargs):
super(<Model>, self).save(*args, **kwargs)
if not self.pk:
self.slug = <slugify code here>
As for your view/Template, you are specifically selecting all posts using:
android_posts = Android.objects.all()
Passing them to the template, then looping over them with the for loop to display them all.
Instead of this, select only a single object with:
android_post = Android.object.get(pk=<pk value>)
Edit after you added your urls.py:
You can get the unique object for a slug with:
android_post = get_object_or_404(Android, slug=slug)
The use of get_object_or_404 will also handle the case where that record doesn't exist.
You haven't posted your urls.py, so not sure how you're linking to this view, but if it includes the slug in the url, you will be able to get this in the view. My guess is you're not accessing via slug in the url, but via the id field.
Personally, when I slugify some text, I always include the id - it is a better way of ensuring uniqueness. By specifying unique=True on your non-pk slug field, you are likely restricting the titles people can use (2 people couldn't use the same title then!)
To give you an example, this is how I am doing it on one of my models:
def save(self, *args, **kwargs):
if not self.id or not self.slug:
super(Android, self).save(*args, **kwargs)
self.slug = slugify(f"{self.title} {str(self.id)}")
super(Android, self).save(*args, **kwargs)
This slug will always be unique because it includes id - and 2 people could have a record with the same title value without the system objecting.

How can I make Django slug and id work together?

I want my URL to contain both id and slug like StackOverflow, but the slug is not working properly. Instead of being like this:
www.example.com/games/155/far-cry-5
the URL is like this:
www.example.com/games/155/<bound%20method%20Game.slug%20of%20<Game:%20Far%20Cry%205>>
My models.py :
class Game(models.Model):
name = models.CharField(max_length=140)
def slug(self):
return slugify(self.name)
def get_absolute_url(self):
return reverse('core:gamedetail', kwargs={'pk': self.id, 'slug': self.slug})
My views.py :
class GameDetail(DetailView):
model = Game
template_name = 'core/game_detail.html'
context_object_name = 'game_detail'
My urls.py :
path('<int:pk>/<slug>', views.GameDetail.as_view(), name='gamedetail')
Call the slug() method to get the value.
return reverse('core:gamedetail', kwargs={'pk': self.id, 'slug': self.slug()})
Or define it as a propery of the class
#property
def slug(self):
...

Populating foreign key fields in Django

In my first app (course) I'm creating courses. Each course has number of chapters and each chapter has quiz. I'm trying to create quiz using second app (quiz). models.py (quiz) :
class Quiz(models.Model):
coursechapter = models.ForeignKey(CourseChapter)
name = models.CharField(max_length=255, verbose_name=u'Quiz name',)
creator = models.ForeignKey(User)
creation = models.DateField(auto_now_add=True)
def __unicode__ (self):
return self.name
class Question(models.Model):
quiz = models.ForeignKey(Quiz)
text = models.CharField(max_length=255, verbose_name=u'Question\'s text')
class QuestionAnswer(models.Model):
question = models.ForeignKey(Question)
text = models.CharField(max_length=255, verbose_name=u'Answer\'s text')
is_valid = models.BooleanField(default=False)
class UserAnswer(models.Model):
answer = models.ForeignKey(QuestionAnswer)
I have template for creating courses, inside that template i have link (Add chapters) which takes me to another template(view) for creating chapters. Inside i have link to create quiz for that specific chapter. That link leads to url: /quiz/new (using url.py from quiz app) which is represented by view.py (from quiz app) .
Problem is i dont know how to get id of chapter for which I'm creating quiz. Example of chapter url (one before user click Create Quiz) /course/new/chapter/197/ , is it possible to somehow send chapter_id (197) through link or is there any other way?
views.py(Quiz):
class CreateQuizView(CreateChapterView):
model = Quiz
template_name = 'quiz/create_quiz.html'
fields = '__all__'
def dispatch(self, request, *args, **kwargs):
self.pk = kwargs.get('pk')
return super(CreateQuizView, self).dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse('quiz-list',
kwargs={'pk': Quiz.objects.latest('id').id})
def get_context_data(self, **kwargs):
context = super(CreateQuizView, self).get_context_data(**kwargs)
return context
views.py(Course):
class CreateChapterView(CreateView, GroupRequiredMixin):
model = CourseChapter
template_name = 'course/add_chapter.html'
fields = '__all__'
def dispatch(self, request, *args, **kwargs):
self.pk = kwargs.get('pk')
return super(CreateChapterView, self).dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse('courses-chapters',
kwargs={'pk': Course.objects.latest('id').id})
def get_context_data(self, **kwargs):
context = super(CreateChapterView, self).get_context_data(**kwargs)
context['chapter'] = CourseChapterForm
context['chapters'] = CourseChapter.objects.all()
context['last'] = Course.objects.latest('id')
context['courses'] = Course.objects.all()
context['action'] = reverse('courses-chapters',
kwargs={'pk': Course.objects.latest('id').id})
context['kw'] = self.kwargs
context['quiz'] = QuizForm()
context['question'] = QuestionForm()
context['answer'] = QuestionAnswerForm
return context
def form_valid(self, form):
self.object = form.save()
# chapters = CourseChapter.objects.filter(course_id=Course.id)
return HttpResponseRedirect(self.get_success_url())
urls.py (main)
url(r'^course/', include('course.urls')),
url(r'^quiz/', include('quiz.urls', namespace="quiz")),
urls (course)
url(r'^new/$', course.views.CreateCourseView.as_view(),
name='courses-new',),
url(r'^new/(?P<pk>\d+)/$', course.views.CreateChapterView.as_view(),
name='courses-chapters'),
url(r'^edit/(?P<pk>\d+)/$', course.views.UpdateCourseView.as_view(),
name='courses-edit',),
url(r'^new/chapter/(?P<pk>\d+)/$', course.views.CreateChapterView.as_view(),
name='chapter-content',),
url(r'^edit/chapters/(?P<pk>\d+)/$', course.views.UpdateChapterView.as_view(),
name='chapters-edit',),
urls (quiz):
urlpatterns = [
url(r'^$', quiz.views.ListQuizView.as_view(),
name='quiz-list',),
url(r'^new/$', quiz.views.CreateQuizView.as_view(),
name='quiz-new',),
]
You mentioned that you have a link for creating quiz in your page where you create chapters. I suggest you create link right there itself.
For eg,
lets say you have course 'Learn Python' which has chapter called Introduction and it has id 7, you can format your url as such /add-quiz/chapter/chapter_id. You will have chapter in the page if you pass it from the view.
Change your url format from /quiz/new to something like /quiz/new/<chapter id>.
You can fetch these chapter id from your create quiz view.