Django model object with dynamically added attributes - python

I'm looking for a way to dynamically add extra attributes to django model instances so that they are accessible in the template
For example,
in models.py
class Foo(models.Model):
name = models.TextField()
in views.py
def my_view(request):
f = Foo.objects.get(id=1234)
# method in question
f.____add_extra_attribute____('extra_attribute', attr_value)
return render(request, 'my_template.html', {'foo_instance':f})
in my_template.html
<h1>{{ foo_instance.name }}</h1>
<p>{{ foo_instance.extra_attribute }}</p>
is there a way to accomplish this without rendering the instance as a dictionary instead of a Foo model object?

Following #mgilson's comment, you can pass the extra_attribute to the template in a separate context variable:
def my_view(request):
f = Foo.objects.get(id=1234)
extra_attribute = attr_value
return render(request, 'my_template.html',
{'foo_instance': f, 'extra_attribute': attr_value})
If you still want to set the attribute on a model instance, you can set it directly:
f.extra_attribute = attr_value

Look at the example below. This is real code from my project. Here I have attached an attribute to the model object itself.
My model:
class Task(models.Model):
name = models.CharField(max_length=500)
description = models.TextField(max_length=500, blank=True)
assignee = models.ForeignKey(Employee, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True)
report = models.ForeignKey(UtilizationReport, on_delete=models.CASCADE, null=True)
role = models.ForeignKey(Role, on_delete=models.CASCADE, null=True, blank=True)
billable_efforts = models.FloatField(
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
)
created_at = models.DateTimeField(default=timezone.now, editable=False)
reviewed = models.BooleanField(default=False)
In view.py:
task = Task.objects.get(id=1) # Just an example...won't be really using get with id
task_rate = BillingCatagoryRate.objects.get(rate_card=invoice.rate_card, billing_category=task.assignee.billing_category).billing_rate
task.task_price = task_rate * task.billable_efforts # Note: task_price is NOT a model field.
In the template:
<td class="text-center">{{ task.task_price }}</td>

Related

Cannot query "Product": Must be "Comment" instance

I'm trying to add a commenting and replying system to my products model but I can't add replies to comment.
This is being done in the same page where the product details are being shown to the user.
Edit:
I'm getting a Cannot assign "<Product: Test Product>": "Reply.comment" must be a "Comment" instance. error at new_reply = Reply(content=content, author=self.request.user, comment=self.get_object())
views.py:
class ProductFeedbackView(DetailView):
model = Product
template_name = 'store/product_feedback.html'
def get_context_data(self , **kwargs):
data = super().get_context_data(**kwargs)
connected_comments = Comment.objects.filter(product=self.get_object())
number_of_comments = connected_comments.count()
data['comments'] = connected_comments
data['no_of_comments'] = number_of_comments
data['comment_form'] = CommentForm()
connected_replies = Reply.objects.filter(comment=self.get_object())
number_of_replies = connected_replies.count()
data['replies'] = connected_replies
data['no_of_replies'] = number_of_replies
data['reply_form'] = ReplyForm()
return data
def post(self , request , *args , **kwargs):
if self.request.method == 'POST':
reply_form = ReplyForm(self.request.POST)
if reply_form.is_valid():
content = reply_form.cleaned_data['content']
new_reply = Reply(content=content, author=self.request.user, comment=self.get_object())
new_reply.save()
return redirect(self.request.path_info)
if self.request.method == 'POST':
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
content = comment_form.cleaned_data['content']
new_comment = Comment(content=content, author=self.request.user, product=self.get_object())
new_comment.save()
return redirect(self.request.path_info)
models.py:
class Product(models.Model):
author = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
title = models.CharField(max_length=120, unique=True)
description = models.CharField(max_length=300, blank=True, null=True)
class Comment(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=True, blank=False)
class Reply(models.Model):
comment = models.ForeignKey(Comment, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.TextField(null=True, blank=False)
As the error message suggests, you're trying to assign a Product instance to a field that expects a Comment instance.
This is the line where you try to do this:
connected_replies = Reply.objects.filter(comment=self.get_object())
self.get_object() returns a Product instance as you defined model = Product on your View.
To get the replies connected to your product, you will need to loop over all comments and per comment all its replies as you defined these relations as foreignkeys.
For example:
for comment in connected_comments:
comment_replies = Reply.objects.filter(comment=comment)
#Vincent answer is ok, the error is from wrong model passed to filter of Replay model.
But for remedy to make it easier in template for showing comments and replies to those comments i suggest delete from context
data['replies']
data['no_of_replies']
and in template where you loop through comments (just example):
{% for comment in comments %}
<h1>{{comment}}</h1>
{% for reply in comment.reply_set.all %}
<p>{{ reply }} </p>
{% endfor %}
{% endfor %}
use reverse relationship with reply_set.
Oh, and for optimization add prefetch_related to your query:
Comment.objects.filter(product=self.get_object()).prefetch_related('reply_set')

how to split post view on the same page in django

I have no idea if this question make much sense or not but i am so confused about it. I have a post list view and it is rendering some of the post here.
My question is how can I split the sections of the page.something like this.
what should be the approach of making this kind of view.
this is my posts view.py
posts/view.py
class PostListView(ListView):
model = Post
template_name = 'posts/home.html'
context_object_name = 'posts'
ordering = ['-date_posted']
def get_queryset(self):
if not self.request.user.is_authenticated:
return Post.objects.all()[:10]
else :
return super().get_queryset()
posts/models.py
from django.db import models
from django.utils import timezone
from slugger import AutoSlugField
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.
def upload_location(instance, filename):
return "%s/%s" %(instance.slug, filename)
class Category(models.Model):
title = models.CharField(max_length= 60)
slug = AutoSlugField(populate_from='title')
parent = models.ForeignKey('self',blank=True, null=True ,related_name='children',on_delete=models.CASCADE)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
def __unicode__(self):
return self.title
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=120)
slug = AutoSlugField(populate_from='title')
image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True,
)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
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, slug=None):
return reverse("posts-detail", kwargs={"slug": self.slug})
You have posts assigned to categories. Each post could be assigned only to one category (since you have FK from Post to Category). And you want to display all categories and 10 latest posts under each one.
I see several ways of how to solve that. The easiest one is to extend Category model with property, containing the queryset to retrieve related posts in the way you want them for front page.
class Post(models.Model):
title = models.CharField(max_length=255)
category = models.ForeignKey('Category', on_delete=models.CASCADE, related_name='posts')
date_posted = models.DateTimeField(default=timezone.now)
class Category(models.Model):
title = models.CharField(max_length=255)
#property
def posts_for_frontpage(self):
return self.posts.order_by('-date_posted')[:10]
class FrontpageView(ListView):
model = Category
template_name = 'frontpage.html'
context_object_name = 'categories'
def get_queryset(self):
# select some categories for frontpage
# all by default
return Category.objects.all()
and then in template
{% for category in categories %}
<h1>{{ category.title }}</h1>
<hr />
{% for post in category.posts_for_frontpage %}
<h4>{{ post.title }}</h4>
{% endfor %}
<br />
<br />
{% endfor %}
You could also play with select_related to reduce number of queries and with annotate to get all related posts.

Django Multiple classes in Model.py

I am new to Django and Python. I am trying to create a database of babysitters and one of the objects which can have multiple fields is Education. My first Babysitter has 2 qualifications which produces an error an will not display.
Error Message
views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404
from .models import Babysitter, Education, Work, Reference
# Create your views here.
def all_babysitters(request):
babysitters = Babysitter.objects.all()
return render(request, "babysitters.html", {"babysitters": babysitters})
def babysitter_profile(request, id):
"""A view that displays the profile page of a registered babysitter"""
babysitter = get_object_or_404(Babysitter, id=id)
reference = get_object_or_404(Reference)
education = get_object_or_404(Education)
return render(request, "babysitter_profile.html", {'babysitter': babysitter, 'education': education, 'reference': reference} )
models.py
from django.db import models
from datetime import datetime
# Create your models here.
class Babysitter(models.Model):
list_display = ('firstName', 'lastName', 'minderType')
firstName = models.CharField(max_length=50, blank=True, null=True)
lastName = models.CharField(max_length=50, blank=True, null=True)
minderType = models.CharField(max_length=50, blank=True, null=True)
image = models.ImageField(upload_to='images')
phone = models.CharField(max_length=20, blank=True, null=True)
email = models.CharField(max_length=50, blank=True, null=True)
address1 = models.CharField(max_length=100, null=True)
address2 = models.CharField(max_length=100, null=True)
city = models.CharField(max_length=20, null=True)
county = models.CharField(max_length=100, null=True)
eircode = models.CharField(max_length=7, null=True)
biography = models.TextField(max_length=280,blank=True)
def __str__(self):
return self.firstName + ' ' + self.lastName
class Education(models.Model):
babysitter = models.ForeignKey(Babysitter)
school = models.CharField(max_length=50)
qualification = models.CharField(max_length=50)
fieldOfStudy = models.CharField(max_length=50)
dateFrom = models.DateField(auto_now=False, auto_now_add=False)
dateTo = models.DateField(
auto_now=False, auto_now_add=False, null=True, blank=True)
current = models.BooleanField(default=False)
graduated = models.BooleanField(default=False)
def __str__(self):
return self.school
class Work(models.Model):
babysitter = models.ForeignKey(Babysitter)
family = models.CharField(max_length=50)
role = models.CharField(max_length=50)
location = models.CharField(max_length=50)
dateFrom = models.DateField(auto_now=False, auto_now_add=False)
dateTo = models.DateField(
auto_now=False, auto_now_add=False, null=True, blank=True)
current = models.BooleanField(default=False)
def __str__(self):
return self.work
class Reference(models.Model):
babysitter = models.ForeignKey(Babysitter)
refFamily = models.CharField(max_length=50)
contact = models.CharField(max_length=50)
location = models.CharField(max_length=50)
email = models.CharField(max_length=50, blank=True, null=True)
reference = models.CharField(max_length=300)
date = models.DateField(auto_now=False, auto_now_add=False)
def __str__(self):
return self.refFamily
Can somebody help? I am going to pull my hair out. Thanks
You aren't passing enough information into the calls to get a Reference and Education object:
babysitter = get_object_or_404(Babysitter, id=id)
reference = get_object_or_404(Reference, babysitter_id=babysitter.id)
education = get_object_or_404(Education, babysitter_id=babysitter.id)
The get_object_or_404() function is a shortcut that calls get() underneath, and get() only ever returns a single object (returning more than one will result in the Exception you are seeing).
If you want to see more than one object, then don't use the get_object_or_404 shortcut method (I find those "shortcut" methods to be ugly, personally). Instead, change it to something like:
education_qs = Education.objects.filter(babysitter_id=babysitter.id)
Then loop over that queryset to get the results:
for ed in education_qs:
# Get some data
school = ed.school
You can loop over the queryset in your HTML template, if that's easier.
Update: Here's a better answer that shows how to use querysets:
def babysitter_profile(request, id):
"""A view that displays the profile page of a registered babysitter"""
babysitter = get_object_or_404(Babysitter, id=id)
reference_qs = Reference.objects.filter(babysitter_id=babysitter.id)
education_qs = Education.objects.filter(babysitter_id=babysitter.id)
return render(request, "babysitter_profile.html", {
'babysitter': babysitter,
'education_qs': education_qs,
'reference_qs': reference_qs}
)
Then, in your HTML template, you could do something like the following to show the schools the Babysitter has attended (in a bulleted list):
<ul>
{% for ed in education_qs %}
<li>{{ ed.school }}</li>
{% endfor %}
</ul>
You could do something similar for the Reference data.
I think you should set some parameters to get a specific object, rather than get a bunch of objects.
Just do it like the first instance for get_object_or_404.
reference = get_object_or_404(Reference,id=xx)
education = get_object_or_404(Education,id=yy)
get_object_or_404 returns just 1 object. Use get_list_or_404 if babysitter has "2 qualification" to prevent exception.
babysitter = get_object_or_404(Babysitter, id=id)
education = get_list_or_404(Education, id=babysitter.id)
To prevent MultipleObjectReturned exception.

How can I persist ForeignKey fields into database using Django

I am new to Django I have two models are user and address, here user having two
foreign key fields are 'localaddress', 'permanentaddress'
Address model:
class Address(models.Model):
fulladdress = models.CharField(max_length=1000, null=True, blank=True)
additional_address = models.CharField(max_length=1000, null=True, blank=True)
street_address = models.CharField(max_length=150, null=True, blank=True)
route = models.CharField(max_length=150, null=True, blank=True)
city = models.CharField(max_length=100, null=True, blank=True)
state = models.CharField(max_length=100, null=True, blank=True)
country = models.CharField(max_length=100, null=True, blank=True)
pincode = models.IntegerField(null=True, blank=True)
class Meta:
db_table = 'address'
User model:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
localaddress = models.ForeignKey(Address, on_delete=models.CASCADE, related_name="localaddress", null=True, blank=True)
permanentaddress = models.ForeignKey(Address, on_delete=models.CASCADE, related_name="permanentaddress", null=True, blank=True)
class Meta:
db_table = 'user'
settings.py:
AUTH_USER_MODEL = 'student.User'# changes built-in user model to ours
here both localaddress and permanentaddress having same foreign key (Address model only)
Edit form:
forms.py:
class LocaladdressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['fulladdress', 'additional_address', 'street_address', 'route', 'city', 'state', 'country', 'pincode']
def save(self, commit=True):
user = super(LocaladdressForm, self).save(commit=False)
user.fulladdress = self.cleaned_data['fulladdress']
user.additional_address = self.cleaned_data['additional_address']
user.street_address = self.cleaned_data['street_address']
user.route = self.cleaned_data['route']
user.city = self.cleaned_data['city']
user.state = self.cleaned_data['state']
user.pincode = self.cleaned_data['pincode']
user.country = self.cleaned_data['country']
if commit:
user.save()
return user
views.py:
def address_form(request):
if request.method == 'POST':
address = Address()
form = AddressForm(request.POST, instance=address)
if form.is_valid():
form.save()
userid = request.user.id
User.objects.filter(pk=userid).update(localaddress=address)
return redirect(reverse('student:view_profile'))
else:
args = {'form': form}
return render(request, 'student/addressform.html', args)
else:
form = AddressForm()
args = {'form': form}
return render(request, 'student/addressform.html', args)
i am using for loop to render form elements, i don't know where i did wrong
addressform.html:
<form method="post">
{% csrf_token %}
{{ form.fulladdress }}
{{ form.additional_address}}
{{ form.street_address}}
{{ form.street_address }}
{{ form.city }}
{{ form.state }}
{{ form.pincode }}
{{ form.country }}
</form>
Here i need to render one form localaddress and another form permanentaddress foreign key fields to my template. Initially i am trying first form (localaddress) please help me any one.
Thanks in advance ...
Localaddress form screenshot: Here i did it Autocomplete Address Form using google address api reference link here Autocomplete Address
I am able to persist address and user object but user object creating new object it is not persisting existing object (means localaddress)
I can answer partially about a concept in Django.
Whenever you create a relationship between two Django models you decide which model will be the main model. Over here you have User model and Address model. I am sure in most cases you will agree that User model is the main model and Address will be the sub-model or child model or whatever you call it.
So your ForeignKey field should ALWAYS be on the sub-model. So instead of using ForeignKey on User model like you have done you should do something like this:
address_type_choices = [
(1, 'Local'),
(2, 'Permanent'),
]
class Address(models.Model):
user = models.ForeignKey(User)
type = models.IntegerField(default=1, choices=address_type_choices)
house = model.CharField(max_length=50)
road = model.CharField(max_length=50)
area = model.CharField(max_length=50)
...
Please change the structure of your model and edit the question. I am sure that is what others will suggest too.
Remember, main model will not have foreign key field.
Update: Changed the model to allow you to maintain both local and permanent address.

how to display the name field of Category model instead of [<Category: hockey>, <Category: run>]

what I'm trying to do is for sports category page display links to hockey and running category pages. I think I'm almost there, but instead of [<Category: hockey>, <Category: run>] I want hockey and run to be there.
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
parent_cat = models.ForeignKey('self', null=True, blank=True)
In template I have
{{category.category_set.all}}
my url
url(r'^category/(?P<category_name_url>[\w|\W]+)/$', views.category, name='category'),
can someone please help me out here?
Edit:My full model
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL)
parent_cat = models.ForeignKey('self', null=True, blank=True)
hotCat = models.BooleanField(default=False)
active = models.BooleanField(default=True)
sponsored = models.ForeignKey(Sponsored, null=True, blank=True)
objects = CategoryManager()
def __unicode__(self):
return self.name
def get_absolute_url(self):
return "/category/%s/" %self.name
def get_image_url(self):
return "%s%s" %(settings.MEDIA_URL, self.image)
You should use a for in loop in your template, to display the name for each category, try this :
{% for cat in category.category_set.all %}{{ cat.name }} {% endfor %}
But I assumed that category.category_set.all was the way you get the set of categories to traverse. I'm not sure that this is the correct way to do this (if may be a way do to this I don't know though).
Why don't you get all categories from your view and passing the set directly as something like "categories" to your render() call?
With something like this :
def myview(request):
cats = Category.objects.all()
render(request, "mytemplate.html", {"categories" : cats})

Categories

Resources