Add properties to django models at runtime? - python

Specifically, I have django model objects I'd like to add properties to at runtime. It'd be great if this would work on any python class outside of Django.
I have the following models
models.py
class person(models.Model):
# ...
firstname=models.TextField(max_length=100)
lastname=models.TextField(max_length=100)
type=models.TextField(max_length=100)
address = models.ForeignKey(address, null=True)
class event(models.Model):
date=models.DateTimeField(auto_now=True )
name=models.CharField(max_length=100)
attendees = models.ManyToManyField(person)
def main():
p = person()
p.fisrtsname="Firsty"
p.lastname="Lasty"
p.addnewproperty(newtemporaryproperty)
p.newtemporaryproperty="This is a new temporary property"
when i use
person.newtemporaryproperty=property("")
I get a "Can't set attribute error" when trying to add a value to it.
I may be using it wrong.
EDIT
What i want to do is see if each person in a model has attended an event. and if they have, put a checkbox by their name. Here are the additional related files
tables.py
class AttendeesTable(tables.Table):
firstname = tables.Column()
lastname = tables.Column()
here = tables.TemplateColumn('<input id="attendee_{{ record.pk }}" {{
record.here }} type="checkbox" />',
verbose_name="Here?")
class Meta:
attrs = {'id': 'attendancetable', 'width': '100%', 'class': 'table
table-hover'}
template = 'django_tables2/bootstrap.html'
row_attrs = {
'id': lambda record: str(record.pk),
}
views.py
def markattendancepage(request):
person.here = ""
people= person.objects.all()
groups = group.objects.all()
eventid= 1 #set to 1 for testing
table=None
for p in people:
if event.objects.filter(id=eventid, attendees__pk=p.id).exists():
p.here = "checked"
else:
p.here = ""
table = app.tables.AttendeesTable(people)
RequestConfig(request, paginate=False).configure(table)
return render(request, 'app/user/attendancechecker.html', {'attendeetable':table})
pass

Because django model instances often get reloaded behind the scenes, you probably don't want to set attributes at runtime, because they can easily get lost. Instead, what you might want to do is have a property (or a method) in the class definition such as
class Person(models.Model):
first_name=models.CharField(max_length=100)
last_name=models.CharField(max_length=100)
#property
def is_in_category(self):
# 'calculation' return a boolean
return True if something else False
Then if you have any particular instance of Person you can check the is_in_category property
for person in Person.objects.all():
if person.is_in_category:
# do something
This also works in templates... for instance if you wanted to make a table of people
<table><tr><th>Name</th><th>In category?</th></tr>
{% for person in people %}
<tr>
<td>{{ person.first_name }}, {{person.last_name}}</td>
<td>{% if person.is_in_category %}Yes{% else %}No{% endif %}</td>
</tr>
{% endfor %}
</table>
However, because properties only exist as Python constructs, you cannot use SQL queries based on this property.
# this will not work
people_in_category = Person.objects.filter(is_in_category=False)
If you want to perform queries like this, you would need to create a field on the model or a related model or otherwise come up with an SQL expression that is the equivalent of the property.
Edit:
Given your models, you can perform a query that should do the same thing. Your event model has an attendees field which would be what you're looking for and would be the way to do this since it appears you have the event id in-hand and access to the event model.
evnt = event.objects.get(pk=eventid)
evnt.attendees # people who attended the event
did_not_attend = people.objects.exclude(id__in=evnt.attendees)
You may want to consider making a method on the model manager that annotates a query for the same effect that the property gives you. For example
class PersonManager(models.Manager):
def for_event(self, evnt):
attended_event = person.objects.filter(id__in=evnt.attendees)
qs = self.get_queryset()
return qs.annotate(attended=Exists(attended_event))
Then if you register this manager with your people model you can do
for p in person.objects.for_event(evnt):
if p.attended:
# they attended the event

Related

Make the number of rows after narrowing-down

This is my serializer.
class MixSerializer(serializers.ModelSerializer):
pub_date = serializers.DateTimeField(format="%m/%d/%Y,%I:%M:%S %p")
new_order = #I want to get the number order
class Meta:
model = Mix
fields = ('id','pub_date','detail','user','u_key')
And I narrowing-down the rows like this below.
def get_queryset(self):
queryset = Mix.objects.all()
u_key = self.request.query_params.get('u_key')
if u_key is not None:
queryset = queryset.filter(u_key=u_key)
return queryset
For example, it returns the 30 items from 100 items.
so id should be (1,4,5,6,9,11,13...) like this,
However I want to get the number new_order (1,2,3,4,5,6,....)
I guess I should do some trick in Serializer?
or any other way ?
Any help appreciated.
Well ID is the actual ID in the database, which you don't want to change or override in your queryset (or elsewhere such as your template) because then you would be referring to a different model object, which will cause you problems.
If you want to use ID as some sort of ranking then you have some options, referencing my answer here
The easiest way is to use the forloop.counter in a template or enumerate in a view:
# template
{% for object in objects %}
# rank is {{ forloop0.counter }}
{% endfor %}
# views
for index, value in enumerate(queryset):
# order is the index variable
...
If you want to explicitly add the rank to the queryset then you can use annotation:
from django.db.models import Window, F
from django.db.models.functions import DenseRank
queryset = Mix.objects.annotate(
ranking=Window(
expression=DenseRank(),
order_by=[
F('id').desc(),
]))
If you want to get Order Table data, you have to create an Order Serializer and link to this MixSerilizer, Like this,
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('id',)
class MixSerializer(serializers.ModelSerializer):
pub_date = serializers.DateTimeField(format="%m/%d/%Y,%I:%M:%S %p")
new_order = OrderSerializer()
class Meta:
model = Mix
fields = ('id','pub_date','detail','user','u_key','new_order')
models.py
class Mix(models.Model):
----
----
order = models.ForeignKey(Order, related_name=new_order, on_delete=models.CASCADE)
If want to get parent table data into a child table you have to pass "related_name" attribute in a models fields. and also that name in a child table sterilizer.

Not expected django query set response

I'm making a query set with django to list some courses. The problem is when I make the query in the django shell, it returns something like this: <QuerySet [<Course: Course object (1)>,....]>
How can I make it to obtain the table information?
PSD: I make a query set with the users table exactly as I described and I get the expected result. But it can't show the result in the template. So if you can help... Thanks for the help in advance.
class ListCursos( TemplateView):
model1 = User
model2 = Course
template_name = 'plantillas/miscursos.html'
def get_context_data(self, *args, **kwargs):
context = super(ListCursos, self).get_context_data(**kwargs)
context['usuarios'] = User.objects.all()
context['cursos'] = Course.objects.all()
return context
The values of the columns for each instance of a model are as stored as instances variables. You have not provided the definition of one of your models, so I'll just take this for an example.
class Course(models.Model): # example model
name = models.CharField(max_length=10)
students = models.IntegerField()
When you have a queryset of Course models you can access them by index
>>> all_courses = Course.objects.all()
<QuerySet [<Course: Course object (1)>]>
>>> first_course = all_courses[0]
and to acess the values of the selected Course model instance you just type the names for the columns that you have in the class definition. if for example you have the Course model with name history and 10 students then
>>> first_course.name # just type the name of the column
'history'
>>> first_course.students
10
So to access them in a django template, considering that you are passing in the context the Course.objects.all() with a key of "cursos". (like you are doing)
{% for course in cursos %}
<div>{{course.name}}</div>
<div>{{course.students}}</div>
{% endfor %}

How to call a function in template which is defined in a Django model?

I am extremely newbie to Django, Sorry in the advance for asking the wrong question.
I want to create a function to sum the values of a column and call it on my template in Django.
Model:
class Invoice(models.Model):
product_name = models.CharField(max_length=255)
purchase_date = models.DateTimeField(auto_now=True)
cost_price = models.IntegerField()
selling_price = models.IntegerField()
#property
def get_total(self):
amount = Invoice.objects.aggregate(Sum('selling_price'))
return amount
I created this get_total function to sum of the values of selling_price column.
Template Code:
{% for item in query_result %}
<tr>
<td>{{item.product_name}}</td>
<td>{{item.cost_price}}</td>
<td>{{item.selling_price}}</td>
{{item.get_total}}
</tr>
{% endfor %}
Here's the problem, after passing the data, whenever I call this function, the data is shown multiple times in the template.
Here's the output.
Output Image
I know it's quite silly, but I am unable to figure it out.
Help, Please.
You can directly annotate the aggregation, for example:
Invoice.objects.aggregate(amount = Sum('selling_price'))
The resulting dictionary will have a key called amount. If no such alias were specified, it would be the rather long selling_price__sum
Since it returns a dict, you can get the value through:
class YourModel(models.Model):
#property
def get_total(self):
amount = Invoice.objects.aggregate(amount = Sum('selling_price'))['amount']
return amount
# template
{{ item.get_total }} would only get amount value out, not dict
BTW:
Adding extra manager methods is the preferred way to add “table-level”
functionality to your models. (For “row-level” functionality – i.e.,
functions that act on a single instance of a model object
Aggregation is a table level function, since it calculate the sum of multiple objects, which is better defined as manager method instead at model instance level.
class InvoiceManager(models.Manager):
def get_total(self):
return self.aggregate(amount = Sum('selling_price'))['amount']
class Invoice(models.Model):
product_name = models.CharField(max_length=255)
purchase_date = models.DateTimeField(auto_now=True)
cost_price = models.IntegerField()
selling_price = models.IntegerField()
# customized manager
objects=InvoiceManager()
Views
def your_view(request):
...
context = {
'query_result': Invoice.objects.all()
'amount': Invoice.objects.get_total()
}
return render(request, 'index.html', context)

How to filter out duplicate rows from child model before being displayed in Django ListView

I am using two related models in my Django application. The objects so created in the models are being displayed using the listview class. In the child model I can create multiple rows based on some key date. When I try to display values from both the models (linked with an FK field), all the child objects for the respective FK fields are displayed (wherever more than one records are there).
Is there a way that I may use select distinct to filter out duplicate rows. I have tried:
myModel.objects.distinct().order_by('id')
but still getting all the child rows for the parent id.
In my template I am using:
{% for obj in object_list %}
{{ obj.<field1> }} <!-- field1: Parent model field -->
{% for item in obj.<child_model>_set.all %}
{{ item.<field2> }} <!-- field2: Child model field -->
{% endfor %}
{% endfor %}
My question is:
How do I filter out duplicate rows before the data is displayed?
The backend is sqlite3 on Django 2.0.6 / Python 3.6
Edit
This is how the current list is being generated:
The first column is pk of parent model record and the next column is for child records' pk.
What I am trying to get is:
Option 1: Get only the last (date wise) record for the combination of parent/child rec numbers (i.e. for parent record number 32, only the combination 32|156 should be displayed, and those with values for child records 149 and 148 should not be displayed).
OR
Option 2: Get all combination of records grouped by ParentModel pk field and ChildModel pk field to be shown separately in successive rows (as you can see, the multiple values for a parent record (wherever existing), is being shown in the same row, successive columns).
PS. I am sorry things are getting quite dense here.
Edit 2
This is the class view I am using for displaying data:
class myRateListView(ListView):
template_name = "rate_list.html"
context_object_name = 'ratelists'
model = ParentModel
def get_context_data(self, **kwargs):
context = super(myRateListView, self).get_context_data(**kwargs)
context.update({
'rate_item_list': ChildModel.objects.order_by('field3'),
})
return context
def get_queryset(self):
return ParentModel.objects.values('field1', 'childmodel__field2').distinct()
Here I am getting error :
Cannot resolve keyword 'childmodel' into field. Choices are....
I think my class view is wrong??
Edit 3
Models and view details:
models.py
class TptRateDoc(models.Model):
tpt_rate_doc_number = models.IntegerField(null=True, blank=True.....)
loc_from = models.ForeignKey(Location, related_name='locn_from', on_delete=.......)
loc_to = models.ForeignKey(Location, related_name='locn_to', on_delete=.......)
create_date = models.DateField(default=timezone.now,...)
class TptRateItems(models.Model):
tpt_doc_header = models.ForeignKey(TptRateDoc, on_delete=...)
tpt_rate_item_num = models.CharField(max_length=3, default=10,...)
tpt_rate_valid_from_date = models.DateField(null=True, verbose_name='From date')
tpt_rate_valid_to_date = models.DateField(null=True, verbose_name='To date')
tpt_rate = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True,...)
views.py
class TptRateListView(ListView):
template_name = "tpt_rate_list.html"
context_object_name = 'tptrates'
model = TptRateDoc
def get_context_data(self, **kwargs):
context = super(TptRateListView, self).get_context_data(**kwargs)
context.update({
'tpt_rate_item_list': TptRateItems.objects.order_by('tpt_rate_valid_to_date'), # .distinct()
})
return context
def get_queryset(self):
# return TptRateDoc.objects.order_by('loc_from')
return TptRateDoc.objects.values('tpt_rate_doc_number', 'tptrateitems__tpt_rate_item_num').distinct()
Note: The commented out parts are what I had tried earlier.
did you try to catch the returned queryset of distinct filter to a new queryset? I mean like this:(* according docs, distinct will come after order_by())
queryset = myModel.objects.order_by('id').distinct()
and then pass this updated queryset to template.
return render(request, "html_file.html", { "objects": queryset})
On PostgreSQL only, you can pass positional arguments (*fields) in order to specify the names of fields to which the DISTINCT should apply. This translates to a SELECT DISTINCT ON SQL query. Here’s the difference. For a normal distinct() call, the database compares each field in each row when determining which rows are distinct. For a distinct() call with specified field names, the database will only compare the specified field names.
You have not stated when you consider objects to be "distinct". If you do somthing like
queryset = MyModel.objects.distinct()
your queryset will contain all the MyModel instances. Why? Because each instance will have a different id (or pk) even if all other fields are identical. So for distinct() to work you need to specifify the fields to consider, i.e. you need to use values(). So in order to get a queryset containing the distinct values of field2 in your ChildModel you would need to do something like
queryset = ChildModel.objects.values('field2').distinct()
So if I understand you correctly you want to display all "field1" values and associated "field2" values where "field2" values should be unique. What I would recomend is a 2 step approach.
First in your view create a queryset containing distinct combinations of field1 and field2, e.g.:
queryset = ParentModel.objects.values('field1', 'childmodel__field2').distinct()
and then pass this queryset to your template
return render(request, 'parent_child.html', {'objects': queryset})
Then, in order to do the hierachical rendering, you can use the {% regroup %} template tag as described in the docs:
{% regroup objects by field1 as obj_list %}
<ul>
{% for obj in obj_list %}
<li>
{{ obj.grouper }}
<ul>
{% for child in obj.list %}
<li>
{{ child.childmodel__field2 }}
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
As OP has not posted his models these are the models used for this answer
class ParentModel(models.Model):
field1 = models.CharField(max_length=100)
class ChildModel(models.Model):
parentmodel = models.ForeignKey(ParentModel, on_delete=models.CASCADE)
field2 = models.CharField(max_length=100)
field3 = models.IntegerField()

Sum of fields for filtered queryset using django_filters

I have the following view
class AuthorList(FilterView):
model = Author
filterset_class = AuthorFilter
context_object_name = 'authors'
In the template, one of the field is {{ author.value }}, which is an integer.
What I would like to do is to show the sum of all {{ author.value }} in my template, but in a dynamic way (if some filters are used, the sum is updated with the current Queryset).
I have tried adding extra context with get_context_data but I couldn't find out how to make it in a dynamic way.
EDIT
tried this, still not working:
#property
def get_sum_values(self):
sum_values = self.objects.all().aggregate(Sum('value'))['value__sum']
return sum_values
and in the template: {{ authors.get_sum_values }}
I have also tried to add a print statement in the property, but nothing shows up, so I guess the function is not even loaded.
my models:
class Author(models.Model):
name = models.CharField(max_length=50, blank=True, null=True)
value = models.IntegerField(null=True, blank=True)
Have you tried doing the sum in the model as a function ?
#property
def wallet_amount_guests(self):
data_payments = self.user.earnings_set.filter(transaction_type='g').aggregate(Sum('amount'))['amount__sum']
if data_payments == None:
data_payments = 0
return data_payments
The above is just an example I have used before. You can then call in the html as blah.function_name

Categories