DetailView iterating reverse ManyToMany objects - python

Given the Django example in making queries:
class Author(models.Model):
name = models.CharField(max_length=50)
...
def __str__(self):
return self.name
class Entry(models.Model):
...
authors = models.ManyToManyField(Author)
I'd like to have an author DetailView which contains a list of entries for that author. What I have so far:
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super(AuthorDetailView, self).get_context_data(**kwargs)
context['entries'] = Entry.objects.filter(
authors__name=self.object.name)
return context
and in my template:
{% for entry in entries %}
…
{% endfor %}
I'd also prefer to not filter by name but that specific author since name could be non unique.

You could use reverse relationship
context['entries'] = self.object.entry_set.all()
This gives you all Entry objects of that Author.
EDIT:
And why are you using author__name?
You can filter by the object directly:
context['entries'] = Entry.objects.filter(authors=self.object)

Related

Accessing primary key in Django class based view

Accessing primary keys in Django class based view
Let's start from the beginning. I have 2 models, Recipe, and Ingredient. They look like this.
In models.py
class Recipe(models.Model):
name=models.CharField(max_length=20, help_text='Enter the name of this recipe')
description=models.TextField(max_length=75, help_text='Describe your recipe')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('recipe-detail', kwargs={'pk': self.pk`})
class Ingredient(models.Model):
recipe=models.ForeignKey(Recipe, on_delete=models.CASCADE)
ingredient=models.CharField(max_length=100)
class Meta:
ordering = ['ingredient']
def __str__(self):
return self.ingredient
What I want to be able to do is have a detail view, where I can access the Recipe attributes, like the name and description, as well as, be able to loop through the ingredients. This is what I have working so far:
In views.py
def recipe_detail_view(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
context = {
'recipe': recipe,
'ingredients': Ingredient.objects.filter(recipe=pk)
}
return render(request, 'recipes/recipe_detail.html', context=context)
In urls.py
# ...
path('recipes/<str:pk>', views.recipe_detail_view, name='recipe-detail')
# ...
In template
<h1 class="title is-1">{{ recipe.name }}</h1>
<p>{{ recipe.description }}</p>
<h3 class="title">Ingredients</h3>
{% for ingredient in ingredients %}
<h4 class="">{{ ingredient.ingredient.title }}</h3>
{% endfor %}
I am wondering how I could turn this into a class based view however. More specifically, I am wondering how I can access and pass in the primary key to the filter like so:
class RecipeDetailView(generic.DetailView):
model = Recipe
template_name = 'recipes/recipe_detail.html'
context_object_name='recipe'
extra_context = {
'ingredients': Ingredient.objects.filter(recipe=pk),
}
Can anyone help?
You can use get_context_data and get_object to get the data you want to your template.
class RecipeDetailView(generic.DetailView):
model = Recipe
template_name = 'recipes/recipe_detail.html'
context_object_name='recipe'
def get_context_data(self, **kwargs)
ctx = super().get_context_data(**kwargs)
ctx['ingredients'] = Ingredient.objects.filter(recipe=self.get_object().pk)
return ctx

Django Form Dynamic Fields looping over each field from POST and creating records

I'm looking for some advice where to go from here. I've been working on making a Form, which dynamically generates its fields.
The form is working and generating everything correctly. However, I am having issues with how to save the actual form data. I'm looking for each field to save as a new item in a model.
The View Class from view.py
class MaintenanceCheckListForm(LoginRequiredMixin, FormView):
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
form_class = MaintenanceCheckListForm
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
form.cleaned_data
for key, values in form:
MaintenanceCheckList.objects.create(
item = key,
is_compliant = values
)
return super().form_valid(form)
The Form from forms.py
class MaintenanceCheckListForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MaintenanceCheckListForm, self).__init__(*args, **kwargs)
items = Maintenance_Item.objects.all()
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
for item in items:
self.fields[str(item.name)] = forms.ChoiceField(
label=item.name,
choices=CHOICES,
widget=forms.RadioSelect,
initial='F',
)
The Model, from models.py
class MaintenanceCheckList(CommonInfo):
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
id = models.AutoField(primary_key=True)
item = models.CharField(max_length=100)
is_compliant = models.CharField(max_length=20, choices= CHOICES)
I am having trouble accessing the data from the Form when it POST's. I've done some troubleshooting where I have set the values statically in the '''form_valid''' and it appears to generate the correct amounts of entires in the model. However the trouble begins when I attempt to insert the values from the POST.
I receieve the below error, which I believe it is trying to dump all the keys and values into a single item instead of looping over each key, value and creating the item.
DataError at /maintenance/checklist
value too long for type character varying(100)
Request Method: POST
Request URL: http://t1.localhost:8000/maintenance/checklist
Django Version: 3.1.6
Exception Type: DataError
Exception Value:
value too long for type character varying(100)
I'm fairly new to the world of Django (4 weeks and counting so far, and maybe 12 weeks into python). So any assistance would be amazing!
I believe you have somewhat gone on a tangent. There's a simpler solution of using Model formsets for what you want.
First if you want a custom form make that:
from django import forms
class MaintenanceCheckListComplianceForm(forms.ModelForm):
item = forms.CharField(widget = forms.HiddenInput())
is_compliant = forms.ChoiceField(
choices=MaintenanceCheckList.CHOICES,
widget=forms.RadioSelect,
initial='F',
)
class Meta:
model = MaintenanceCheckList
fields = ('item', 'is_compliant')
Next use it along with modelformset_factory in your views:
from django.forms import modelformset_factory
class MaintenanceCheckListFormView(LoginRequiredMixin, FormView): # Changed view name was a bit misleading
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
instances = form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['queryset'] = MaintenanceCheckList.objects.none()
kwargs['initial'] = [{'item': obj['name'], 'is_compliant': 'F'} for obj in Maintenance_Item.objects.all().values('name')]
return kwargs
def get_form(self, form_class=None):
kwargs = self.get_form_kwargs()
extra = len(kwargs['initial'])
form_class = modelformset_factory(MaintenanceCheckList, form=MaintenanceCheckListComplianceForm, extra=extra)
return form_class(**kwargs)
Now in your template:
<form method="post">
{{ form }}
</form>
Or manually render it:
<form method="post">
{{ form.management_form }}
{% for sub_form in form %}
Item: {{ sub_form.item.value }}
{{ sub_form }}
{% endfor %}
</form>
Note: The above usage is a bit weird due to the naming of the formset variable as form by the FormView you should look into improving that a bit.
Note: Looking at the implementation it feels a bit weird to do this. I would advice you to redesign your models a bit. Perhaps a foreign key between your models? It basically feels like you have duplicate data with this implementation.

How to get the content from my database in views.py? [Django]

I am trying to print the content fields from my database,
Here's my models.py file:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
read_time = models.TimeField(null=True, blank=True)
view_count = models.IntegerField(default=0)
Here's my views.py file:-
class PostDetailView(DetailView):
model = Post
def get_object(self):
obj = super().get_object()
obj.view_count += 1
obj.save()
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
all_texts = {
'texts': context.content
}
print(all_texts[texts])
return context
I am trying to access all the data's from the content field from my database,
But the above way is not working, Is there any way I can access all the data's from the content field, because I have to perform some action on these fields, like calculate the read_time of any content, based on the length of it.
You do not have to override the .get_queryset(…) method [Django-doc] for that, since the object is already passed to the context. You can simply render it in the template with:
{{ object.content }}
In case you really need this in the context, you can implement this as:
class PostDetailView(DetailView):
model = Post
# …
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(
texts=self.object.content
)
return context
In case you need all post objects, you can add these to the context:
class PostDetailView(DetailView):
model = Post
# …
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(
texts=self.object.content,
posts=Post.objects.all()
)
return context
and render these as:
{% for post in posts %}
{{ post.content }}
{% endfor %}
It might be better to work with an F expression [Django-doc] when incrementing the view counter to avoid race conditions:
class PostDetailView(DetailView):
model = Post
def get_object(self):
obj = super().get_object()
views = obj.view_count
obj.view_count = F('view_count') + 1
obj.save()
obj.view_count = views+1
return obj
Just query all objects and loop the queryset to manipulate them according to your needs like so:
def your_view(self, **kwargs):
# Get all table entries of Model Post
queryset = Post.objects.all()
# Loop each object in the queryset
for object in queryset:
# Do some logic
print(object.content)
[...]
return (..., ...)
first import models
from . models import Post
then in your function
data=Post.objects.values('content').all()
Now data have all the values in content field
data=[{'content':first_value},{'content':second_value},..like this...]

How to render information from a ForeignKey field through DetailView class?

I have two models (Taxonomia and Distribucion) which are the following:
# models.py file
class Taxonomia(models.Model):
id_cactacea = models.AutoField(primary_key=True)
subfamilia = models.CharField(max_length=200)
class Distribucion(models.Model):
id_distribucion = models.AutoField(primary_key=True)
localidad = models.TextField(null=True, blank=True)
taxonomia = models.ForeignKey(Taxonomia, on_delete=models.CASCADE, default=1)
As you can see in Distribucion there is a one to many relationship with the Taxomia table.
Implement the two models in the "admin.py" file so that you can edit the Distribucion table from Taxonomia
class DistribucionInline(admin.TabularInline):
model = Distribucion
extra = 0
class TaxonomiaAdmin(admin.ModelAdmin):
actions = None # desactivando accion de 'eliminar'
list_per_page = 20
search_fields = ('genero',)
radio_fields = {"estado_conservacion": admin.HORIZONTAL}
inlines = [DistribucionInline]
admin.site.register(Taxonomia, TaxonomiaAdmin)
In turn, the file "view.py" renders the Taxonomia table as follows:
from repositorio.models import Taxonomia, Distribucion
class CactaceaDetail(DetailView):
model = Taxonomia
template_name = 'repositorio/cactacea_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['distribuciones'] = Distribucion.objects.all()
return context
I tried to access to context ['distribuciones'] information from the template as follows without getting any results:
{% for obj in object.distribuciones %}
{{ obj.localidad }}
{% endfor %}
OBS: For each Taxonomia element there will be four elements from the Distribucion table, so I need to use a FOR loop
Is the way I add the information from the Taxonomia table in the "CactaceaDetail" view correct?
Is the way I read the information in the template correct?
How could I visualize all the information that "CactaceaDetail" sends to the template using the DJANGO shell so that I can debug better in the future?
Thank you.
Try removing the "object" from your for loop in the template:
{% for obj in distribuciones %}
{{ obj.localidad }}
{% endfor %}
The reason is because you are passing distribuciones in the regular context, not as part of the class object so you can't reference it with object.distribuciones.

Django: Sort given results of method on model

In Django, how can I sort the results of a method on my model?
class Flashcard(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
deck = models.ForeignKey(Deck, on_delete=models.CASCADE)
question = models.TextField()
answer = models.TextField()
difficulty = models.FloatField(default=2.5)
objects = FlashcardManager()
def __str__(self):
return self.question
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='profile')
bio = models.TextField(max_length=500,null=True, default='',blank=True)
def __str__(self):
return f'{self.user.username} Profile'
def avg_diff_user(self):
avg_diff = Flashcard.objects.filter(owner = self.user).aggregate(Avg('difficulty'))['difficulty__avg']
return avg_diff
So with avg_diff_user, I get each user's average difficulty rating. Which I can then use in my leaderboard template as follows:
<ol>
{% for user in leaderboard_list %}
<li>{{user.username}}: {{user.profile.avg_diff_user|floatformat:2}}</li>
{% endfor %}
</ol>
The results show, but it's not sorted - how can I sort by avg_diff_user? I've read many similar questions on SO, but to no avail. I've tried a different method on my model:
def avg_diff_sorted(self):
avg_diff_sorted = Flashcard.objects.all().annotate(get_avg_diff_user=Avg(Flashcard('difficulty'))['difficulty__avg'].order_by(get_avg_diff_user))
return avg_diff_sorted
Which I don't think is right and didn't return any results in my template. I also tried the following, as suggested in https://stackoverflow.com/a/930894/13290801, which didn't work for me:
def avg_diff_sorted(self):
avg_diff_sorted = sorted(Flashcard.objects.all(), key = lambda p: p.avg_diff)
return avg_diff_sorted
My views:
class LeaderboardView(ListView):
model = User
template_name = 'accounts/leaderboard.html'
context_object_name = 'leaderboard_list'
def get_queryset(self):
return self.model.objects.all()
something like:
leaderboard_list = User.objects.all().annotate(avg_score=Avg('flashcard__difficulty').order_by('-avg_score')
will sort you the users by their average score.
I don't use ListView that often by if you just used a standard view like:
def LeaderboardView(request):
leaderboard_list = ...
context = {'leaderboard_list':leaderboard_list}
return render(request, 'accounts/leaderboard.html', context)
In your html you could do the same:
{% for user in leaderboard_list %}
...
{% endfor %}

Categories