How to append value to the ArrayField in Django with PostgreSQL? - python

I have created a model with the ArrayField in Django Model. I am using PostgreSQL for the database.
I want to create new rows in the database or update the existing rows.
But I can not insert or append data to the ArrayField.
Model
class AttendanceModel(BaseModel):
"""
Model to store employee's attendance data.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='attendance_user')
date = models.DateField()
time = ArrayField(models.TimeField(), blank=True, null=True)
class Meta:
unique_together = [['user', 'date']]
def __str__(self):
return f'{self.user.username} - {self.date}'
View
attend, created = models.AttendanceModel.objects.get_or_create(user=fingerprint.user, date=attendance_date)
attend.time = attend.time.append(attendance.timestamp.time())
attend.save()
The time field does not being created or updated with the new value.
How can I do this?

attend.time = attend.time.append(attendance.timestamp.time())
Here the return value of list.append(...) is being assigned to attend.time, which is None, thus the attend.time is not getting updated.
Try this instead,
attend, created = models.AttendanceModel.objects.get_or_create(
user=fingerprint.user, date=attendance_date
)
attend.time.append(attendance.timestamp.time())
attend.save()

Following this Answer I did this.
from django.db.models import Func, F, Value
.
.
.
attend, created = models.AttendanceModel.objects.get_or_create(user=fingerprint.user, date=attendance_date)
attend.time = Func(F('time'), Value(attendance.timestamp.time()), function='array_append')
attend.save()

Related

save() saves all fields except ManyToMany field

I have a model "Contest" with one m2m field called "teams" which is related to a model "Team".
I overrided the method save. In my save() function (the one that's overriding), I need a queryset (in my save overrinding function) with all the objects related to my team m2m field. The code is self.teams.all() but it won't work because my models is not yet registered in database right ? So I call super().save(*args, **kwargs) now my models is saved and I can get my queryset ?
I can't. The queryset is empty, even if I registered team(s). <QuerySet []>
Why does super.save() save immediately all the fields except the m2m ?
I use exclusively the django admin web site to create models. No manage.py shell or form.
My model :
class Contest(models.Model):
name = models.CharField(max_length=16, primary_key=True, unique=True, default="InactiveContest", blank=True) # Ex: PSGvMNC_09/2017
id = models.IntegerField(default=1)
teams = models.ManyToManyField(Team, verbose_name="opposants")
date = models.DateTimeField(blank=True)
winner = models.ForeignKey(Team, verbose_name='gagnant', related_name='winner', on_delete=models.SET_NULL, blank=True, null=True)
loser = models.ForeignKey(Team, verbose_name='perdant', related_name='loser', on_delete=models.SET_NULL, blank=True, null=True)
bet = models.IntegerField(verbose_name='Nombre de paris', default=0, blank=True, null=0)
active = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self._state.adding:
self.active = False
# Django's id field immitation
last_id = Contest.objects.all().aggregate(Max('id')).get('id__max')
if last_id is not None:
self.id = last_id + 1
super().save(*args, **kwargs)
print(Contest.objects.get(id=self.id)) # Works --> model saved in db... in theory
queryset = self.teams.all() # Empty all the time !
Once save() (the overrinding one) has been executed one time, the problem is solved and next times (modifications) I can get my queryset so I could just use self._state.adding but this method obliges me to save 2 times (creation, editing).
I need to understand why super().save(*args, **kwargs)behaves like this and how can I solve this ?
The reason is that implementation of ManyToManyField does not use the database table of the model. It uses third table for connection between two models.
Each model has its own database table. In your example
model Contest -> table app_contest
model Team -> table app_team
However, app_contest does not have field teams in it. And app_team does not contain field contest.
Instead, there is a third table e.g. called app_contest_team that consists of three columns:
id
contest_id
team_id
And stores pairs of contest_id + team_id that represent connection between contests and teams.
So queryset = self.teams.all() is equal to the SQL expression:
SELECT * FROM app_teams WHERE id in
(SELECT team_id from app_contest_teams WHERE contest_id = '{self.id}');
Thus, you can see that you cannot anyhow manipulate ManyToManyField without having an instance initially saved in database. Because only after inserting into table app_contest it receives unique ID which can be later used for creating entries in the third table app_contest_teams.
And also because of that - save() is not required when you add new object to ManyToManyField because table of the model is not affected

Django model reference and manipulation

I have the following models in Django that have a structure as follows:
class Office_Accounts(models.Model):
accountid = models.EmailField(max_length=200, unique=True)
validtill = models.DateField(default=datetime.now)
limit = models.CharField(max_length=2)
class Device(models.Model):
device_type = models.ForeignKey(DeviceType,to_field='device_type')
serial_number = models.CharField(max_length=200,unique=True)
in_use_by = models.ForeignKey(User,to_field='username')
brand = models.CharField(max_length=200,default="-", null=False)
model = models.CharField(max_length=200,default="-", null=False)
type_number = models.CharField(max_length=200,blank=True,null=True, default = None)
mac_address = models.CharField(max_length=200,blank=True,null=True, default = None)
invoice = models.FileField(upload_to='Device_Invoice', null=True, blank = True)
msofficeaccount = models.ForeignKey(Office_Accounts, to_field="accountid")
class Meta:
verbose_name_plural = "Devices"
def full_name(self):
return self.device_type + self.serial_number + self.brand
I will display both of the models in admin.py.
Now, I want to display the count of each accountid present in the field "msofficeaccount" (present in Device Models) in my admin page of Office_Accounts model. For an example if xyz#abc.com appears in 10 rows of msofficeaccount field then, the count should be displayed as 10 in Office_Accounts admin page. Can anyone please guide me how should I approach this problem to solve it?
You could add a method to your admin class that returns the count of related devices for each office_account, but that would be very inefficient. Instead you can override get_queryset to annotate the count from a database aggregation function:
from django.db.models import Count
class Office_AccountsAdmin(admin.ModelAdmin):
list_display = (..., 'device_count')
...
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(device_count=Count('device'))
(On a minor note, Python style is always to use CamelCase for class names, and Django style is to use singular model names, so your model should really be called OfficeAccount.)

Django-Tables2 add extra columns from dictionary

I apologize if this question has been asked before but I couldn't find my specific use case answered.
I have a table that displays basic product information. Product details such as price, number of sales, and number of sellers are scraped periodically and stored in a separate database table. Now I want to display both the basic product information and scraped details in one table on the frontend using tables2. To do this, I wrote a function in my Product model to fetch the latest details and return them as a dictionary this way I can use a single Accessor call.
# models.py
class Product(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=256)
brand = models.ForeignKey(Brand)
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES)
def __unicode__(self):
return self.name
def currentState(self):
currentDetailState = ProductDetailsState.objects.filter(
product=self
).latest('created_at')
# return current details as a dictionary
return {
price: currentDetailState.price,
num_sellers: currentDetailState.num_sellers,
num_sales: currentDetailState.num_sales
}
class ProductDetailsState(models.Model):
product = models.ForeignKey(Product)
created_at = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=6, decimal_places=2, null=True)
num_sellers = models.IntegerField(null=True)
num_sales = models.IntegerField(null=True)
def __unicode__(self):
return self.created_at
# tables.py
class ProductTable(tables.Table):
productBrand = tables.Column(
accessor=Accessor('brand.name'),
verbose_name='Brand'
)
currentRank = tables.Column(
accessor=Accessor('currentRank')
)
class Meta:
model = Product
...
How do I now use this returned dictionary and split it into columns in my Product table? Is there another way to use an Accessor than how I am doing it?
You can use the Accessor to traverse into the dict, so something like this should work:
class ProductTable(tables.Table):
# brand is the name of the model field, if you use that as the column name,
# and you have the __unicode__ you have now, the __unicode__ will get called,
# so you can get away with jus this:
brand = tables.Column(verbose_name='Brand')
currentRank = tables.Column()
# ordering on the value of a dict key is not possible, so better to disable it.
price = tables.Column(accessor=tables.A('currentState.price'), orderable=False)
num_sellers = tables.Column(accessor=tables.A('currentState.num_sellers'), orderable=False)
num_sales = tables.Column(accessor=tables.A('currentState.num_sales'), orderable=False)
class Meta:
model = Product
While this works, sorting is also nice to have. In order to do that, your 'currentState' method is a bit in the way, you should change the QuerySet you pass to the table. This view shows how that could work:
from django.db.models import F, Max
from django.shortcuts import render
from django_tables2 import RequestConfig
from .models import Product, ProductDetailsState
from .tables import ProductTable
def table(request):
# first, we make a list of the post recent ProductDetailState instances
# for each Product.
# This assumes the id's increase with the values of created_at,
# which probably is a fair assumption in most cases.
# If not, this query should be altered a bit.
current_state_ids = Product.objects.annotate(current_id=Max('productdetailsstate__id')) \
.values_list('current_id', flat=True)
data = Product.objects.filter(productdetailsstate__pk__in=current_state_ids)
# add annotations to make the table definition cleaner.
data = data.annotate(
price=F('productdetailsstate__price'),
num_sellers=F('productdetailsstate__num_sellers'),
num_sales=F('productdetailsstate__num_sales')
)
table = ProductTable(data)
RequestConfig(request).configure(table)
return render(request, 'table.html', {'table': table})
This simplifies the table definition, using the annotations created above:
class ProductTable(tables.Table):
brand = tables.Column(verbose_name='Brand')
currentRank = tables.Column()
price = tables.Column()
num_sellers = tables.Column()
num_sales = tables.Column()
class Meta:
model = Product
You can find the complete working django project at github

'NoneType' object has no attribute 'to_bytes'

New on python and having a hard time with solving error codes.
I have a form which adds rows to a postgresql databse. the form has an autofield which is primary key inside my models.py. Adding rows as such works, and the uniqueid fields counts up like inteded (1,2,3,...)
models.py:
class forwards(models.Model):
uniqueid = models.AutoField(primary_key=True)
user = models.CharField(max_length = 150)
urlA = models.CharField(max_length = 254)
counterA = models.DecimalField( max_digits=19, decimal_places=0,default=Decimal('0'))
urlB = models.CharField(max_length = 254)
counterB = models.DecimalField( max_digits=19, decimal_places=0,default=Decimal('0'))
timestamp = models.DateTimeField('date created', auto_now_add=True)
shortcodeurl = models.CharField(max_length = 254)
forms.py:
class AddUrlForm(forms.ModelForm):
class Meta:
model = forwards
# fields = '__all__'
exclude = ["user", "counterA", "counterB", "shortcodeurl", "uniqueid"]
The goal is to use the primary key value (which should be an integer according to here), transform it into "bytes" and then do a bytes-to-base64 conversion to create a shortcode-url. I want to store this shortcode inside the table. I try to do this in the views.py
views.py
def forwardthis(request):
forwardform = AddUrlForm(request.POST or None)
if forwardform.is_valid():
forward = forwardform.save(commit=False)
forward.user = request.user.username
uniqueid_local = forward.uniqueid
print(uniqueid_local)
uniqueid_local_bytes = uniqueid_local.to_bytes(3, byteorder='big')
shortcodeurl_local = urlsafe_base64_encode(uniqueid_local_bytes)
forward.shortcodeurl = shortcodeurl_local
forward.save()
My Problem:
I don't succeed in creating this shortcode URL and am getting an "NoneType" error. I tried modifying the models.py into BigIntegerField and IntegerField, but that didn't work. Adding " default=0 " to uniqueid = models.AutoField(primary_key=True) generated no error the first time I submitted a form, but then, when submitting a second form, it gives an error null value in column "timestamp" violates not-null constraint
To me, it looks like the uniqueid is not recognised like an integer. How to fix this?
Thanks for the help!
AutoFields are set by the database itself, so don't get a value until after you save. But you have not saved at that point, because you passed commit=False to the form save; this creates an instance in memory but does not send it to the db yet.
If you want this to work, you will have to remove that commit=False and accept the (tiny) cost of saving to the db twice.

Calculate a number in a Django model from another model's data

I want to take data (amount_spent) from the field of each user and add those numbers up and display them in another field (total_revenue) from a different model (RevenueInfo).
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import models
from django import forms, views
# Create your models here.
#LoginInfo is being used, LoginForms in forms.py is
class LoginInfo(models.Model):
username = models.CharField('', max_length=10)
password = models.CharField('', max_length=15)
class ExtendedProfile(models.Model):
user = models.OneToOneField(User)
amount_spent = models.DecimalField(max_digits=6, decimal_places=2)
class RevenueInfo(models.Model):
total_amount_spent = models.DecimalField(max_digits=6, decimal_places=2, default=0)
total_revenue = models.ForeignKey(ExtendedProfile, null=True)
class Product(models.Model):
category = models.CharField(max_length=100)
name = models.CharField(max_length=100)
description = models.TextField()
#photo = models.ImageField()
price_CAD = models.DecimalField(max_digits=6, decimal_places=2)
quantity = models.DecimalField(max_digits=2, decimal_places=0, null=True)
How could I go about this? Would I iterate of each Usermodel and find User.amount_spent then add that to RevenueInfo.total_revenue? I'm not sure how to put that into code. Also I'm pretty sure I don't need both total_amount_spent and total_revenue but I feel like I need a ForeignKey
You could add a classmethod to the ExtendedProfile model to aggregate the amount_spent value for each User (which bypasses the need for a separate RevenueInfo model):
from django.db.models import Sum
class ExtendedProfile(models.Model):
....
#classmethod
def total_user_spend(cls):
return cls.objects.aggregate(total=Sum('amount_spent'))
Then you can get the total spend using ExtendedProfile.total_user_spend():
>>> ExtendedProfile.total_user_spend()
{'total': Decimal('1234.00')}
Yes, you can write a method for that in your model. There are 2 ways for it.
1) Writing a method that calculates the values and sets it to a instance value.
2) Writing a method that calculates the value and directly returns it.
For example purpose, here is the code for 2nd type.
# models.py
def total_amount_spent(self):
temp_values = [int(user.amount_spent) for user in ExtendedProfile.objects.all()]
return sum(temp_values)
And for using that value in views , but remeber it would be an integer by default
#views.py
value = RevenueInfo.total_amount_spent()
Avoid iterating over database entities in python (it can get really slow). Look into aggregation, it allows you to efficiently get sum (average, max, min, etc...) of values in a database:
>>> from django.db.models import Sum
>>> ExtendedProfile.objects.all().aggregate(Sum('amount_spent'))
{'amount_spent__sum': Decimal('1234.56')}
>>> # ... do whatever you want with the return value

Categories

Resources