Can't update value of IntegerField of Django 1.8 - python

class UserProfile(models.Model):
user = models.OneToOneField(User)
how_many_new_notifications = models.IntegerField(null=True,blank=True,default=0)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
In views.py function which is 100% called and whom is present
whom.profile.how_many_new_notifications += 1
whom.save()
Whatever, how_many_new_notifications is still equal zero and not incremented , despite everything else is correct
Also tried something like this:
if whom.profile.how_many_new_notifications == None:
whom.profile.how_many_new_notifications = 1
else:
varible_number_of_notifications = int( whom.profile.how_many_new_notifications)
whom.profile.how_many_new_notifications = varible_number_of_notifications + 1
Get no errors in log, is there any reason why this code wouldn't work, or should I search for issues in other places?

User.profile is a property that gets a new copy of the profile each time it is used.
So when you do
user.profile.how_many_notifications += 1
user.profile.save()
Each line uses its own copy of the profile, the two Python objects are unrelated.
So you need to do
profile = user.profile
profile.how_many_notifications += 1
profile.save()
But using a profile property like that is a bit odd -- you have a OneToOneField, and a related property is already automatically defined as the lower case name of your class. So
user.userprofile.how_many_new_notifications += 1
user.userprofile.save()
Should also work. If you want to change the name userprofile, use related_name:
user = models.OneToOneField(User, related_name='profile')
And then it works with user.profile.

Related

django save a new instance of object

I am trying to update a user "karma points" whenever a user posts something. For this, i first created a new model called Myuser that allows for points property:
class Myuser(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
points=models.IntegerField(default=1)
And then in my view.py post_new() function, I tried to update the score:
u=User.objects.get(username=request.user.username)
u.myuser.points=u.myuser.points+5
u.save()
but then i notice that rather than update the points field, it just saves a new instance with the same user id but updated score. I thought .save() is supposed to update exisiting copy.
This is what i did for object initiation
u=User.objects.get(username=request.user.username)
Myuser.objects.create(user_id=u.id, points=1)
Edit: I think the problem might be model initiation. When I initiate an instance and check
u=User.objects.get(username='barkthinks') #a registered username
Myuser.objects.create(user_id=u.id, points=10)
<Myuser: Myuser object (6052d3844fbcaa988e993c30)>
When I do:
Myuser.objects.all()
I will get this result
QuerySet [<Myuser: Myuser object (None)>
when I do .save(), sometimes I keep bumping into this error:
TypeError: Field 'id' expected a number but got ObjectId('6052d1054fbcaa988e993c2b').
Get the Unique Myuser instance and update the model instead
u=User.objects.get(username=request.user.username)
myuser = Myuser.objects.get(user=u)
myuser.points += 5
myuser.save()
to update any instance of a model,
variable = Models.objects.filter(conditions).update(attribute_to_update = update_value)
get the instance of the model using filter and condition, then use the .update to update values
models.py :
class Myuser(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
points=models.IntegerField(default=1)
views.py :
u=User.objects.get(username='barkthinks')
new_user_instance = Myuser.objects.create(user = u , points=10)
And then in the view.py post_new() function,
my_user = MyUser.objects.get(user__id = request.user.id)
temp = my_user.points
updated_instance = Myuser.objects.get(User__id = request.user.id).update(points = temp + 10)
try at first check if there is Myuser instance if not create new one, but if there is Myuser class instance for that user, then update that instance by doing this code.
u=User.objects.get(username='barkthinks')
try:
myuser_instance = Myuser.objects.get(user=u)
myuser_instance.points += 5
myuser_instance.save()
except ObjectDoesNotExist:
Myuser.objects.create(user=u, points=10)

Django signal to follow/unfollow

I have a signal that looks like this:
#receiver([post_save, post_delete], sender=Following)
def increment_follow_count(instance, created=False, **kwargs):
if created:
instance.follower.following_count += 1
instance.target.follower_count += 1
else:
instance.follower.following_count -= 1
instance.target.follower_count -= 1
When a user follows another user, it works correctly. However, when that same user unfollows that user, only the person that the user followed (target) has their follower count decremented, but the user's following count is not decremented. Why is this behavior happening and how can I fix it?
Model:
class Following(models.Model):
target = models.ForeignKey('User', related_name='followers', on_delete=models.CASCADE, null=True)
follower = models.ForeignKey('User', related_name='targets', on_delete=models.CASCADE, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{} is followed by {}'.format(self.target, self.follower)
Code to follow/unfollow user
def follow_unfollow(follower, target):
# Query to see if the following exists or not
following = target.followers.filter(follower=follower)
if following.exists():
following.delete()
else:
target.followers.create(follower=follower)
target.save()
follower.save()
increment_follow_count signal contains increment logic but no save logic and saving is done is another follow_unfollow method?
First, increment is better to be atomic, to make sure no changes are lost.
Atomic increment can be achieved using F() expressions.
from django.db.models import F
#receiver([post_save, post_delete], sender=Following)
def increment_follow_count(instance, created=False, **kwargs):
if created:
User.objects.filter(
pk=instance.follower_id
).update(
following_count=F('following_count') + 1
)
User.objects.filter(
pk=instance.target_id
).update(
following_count=F('following_count') + 1
)
else:
User.objects.filter(
pk=instance.follower_id
).update(
following_count=F('following_count') - 1
)
User.objects.filter(
pk=instance.target_id
).update(
following_count=F('following_count') - 1
)
Here increment is not only atomic, but changes are immediately saved
in the database in the same method.
Also, I would suggest to remove target.save() and follower.save() in follow_unfollow - as it overwrites instance in database with the values in memory, and this should not be the case, at least for following_count as its increment logic is in signal. If in follow_unfollow method some changes to fields, other than followng_count are done - then save() should be called with update_fields list to update only changed fields.
Regular += 1 takes current in-memory instance field value, i.e. count=5, increment (count=6), and later, when save is called, it is being saved as update count=6. And during this time value might have already changed in database many times (and update will set it to 6 regardless), especially under load / simultaneous actions.
With atomic increment logic is moved from python to database - and will increment actual value at the time transaction is made.

Django: Transactions and how to avoid wrong counting?

I am currently struggling with a topic connected to transactions. I implemented a discount functionality. Whenever a sale is made with a discount code, the counter redeemed_quantity is increased by + 1.
Now I thought about the case. What if one or more users redeem a discount at the same time? Assuming redeemed_quantity is 10. User 1 buys the product and redeemed_quantity increases by +1 = 11. Now User 2 clicked on 'Pay' at the same time and again redeemed_quantity increases by +1 = 11. Even so, it should be 12. I learned about #transaction.atomic but I think the way I implemented them here will not help me with what I am actually trying to prevent. Can anyone help me with that?
view.py
class IndexView(TemplateView):
template_name = 'website/index.html'
initial_price_of_course = 100000 # TODO: Move to settings
def check_discount_and_get_price(self):
discount_code_get = self.request.GET.get('discount')
discount_code = Discount.objects.filter(code=discount_code_get).first()
if discount_code:
discount_available = discount_code.available()
if not discount_available:
messages.add_message(
self.request,
messages.WARNING,
'Discount not available anymore.'
)
if discount_code and discount_available:
return discount_code, self.initial_price_of_course - discount_code.value
else:
return discount_code, self.initial_price_of_course
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['stripe_pub_key'] = settings.STRIPE_PUB_KEY
discount_object, course_price = self.check_discount_and_get_price()
context['course_price'] = course_price
return context
#transaction.atomic
def post(self, request, *args, **kwargs):
stripe.api_key = settings.STRIPE_SECRET_KEY
token = request.POST.get('stripeToken')
email = request.POST.get('stripeEmail')
discount_object, course_price = self.check_discount_and_get_price()
charge = stripe.Charge.create(
amount=course_price,
currency='EUR',
description='My Description',
source=token,
receipt_email=email,
)
if charge.paid:
if discount_object:
discount_object.redeemed_quantity += 1
discount_object.save()
order = Order(
total_gross=course_price,
discount=discount_object
)
order.save()
return redirect('website:index')
models.py
class Discount(TimeStampedModel):
code = models.CharField(max_length=20)
value = models.IntegerField() # Smallest currency unit, as amount charged
max_quantity = models.IntegerField()
redeemed_quantity = models.IntegerField(default=0)
def available(self):
available_quantity = self.max_quantity - self.redeemed_quantity
if available_quantity > 0:
return True
class Order(TimeStampedModel):
total_gross = models.IntegerField()
discount = models.ForeignKey(
Discount,
on_delete=models.PROTECT, # Can't delete discount if used.
related_name='orders',
null=True,
You can pass the handling of the incrementation to the database in order to avoid the race condition in your code by using django's F expression:
from django.db.models import F
# ...
discount_object.redeemed_quantity = F('redeemed_quantity') + 1
discount_object.save()
From the docs with a completely analogous example:
Although reporter.stories_filed = F('stories_filed') + 1 looks like a normal Python assignment of value to an instance attribute, in fact it’s an SQL construct describing an operation on the database.
When Django encounters an instance of F(), it overrides the standard Python operators to create an encapsulated SQL expression; in this case, one which instructs the database to increment the database field represented by reporter.stories_filed.
Django is a piece of a synchronous code. It means that every request you make to the server is processed individually. This problem could arise, when there are multiple server-workers (for example uwsgi workers), but again - it's practically impossible to do this. We run a webshop application with multiple workers and something like this never happend.
But back to the question - if you want to query the database to increase a value by one, see schwobaseggl's answer.
The last thing is that I think you misunderstand what transaction.atomic() does. Simply put it rolls back any queries made to the database in a function if function exits with an error to the state when function was called. See this answer and this piece of documentation. Maybe it will clear some things up.

When Django models field is empty, set value to the Default value

Whenever the user doesn't add a value, I need my Django models to replace the otherwise empty field with the value set in default.
My models looks like this:
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
max_num_per_day = models.IntegerField(blank=True, null=True, default=0)
I tried every combination of null, blank and default but no matter what I do, the fields gets replaced by null instead of '00:00:00' and 0.
Is there anyway I can force it to the default value whenever the field is empty?
you can set up your form with a default function like:
class YourForm(forms.Form):
.....
def clean_field(self):
data = self.cleaned_data['not_before']
if not data:
data = '00:00:00'
or write a function in your model like:
class Molde(models.Model):
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
def time(self):
if self.not_before:
return self.not_before
else:
return '00:00:00'
In this case you would call the function instead of the model field itself. You can also take a look at this.
Hope that helps.
from what I understood from your question is you just want to set it to default. you can use:
https://code.djangoproject.com/ticket/6754
don't
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
instead,
import datetime
not_before = models.TimeField(default=datetime.time(0,0))
max_num_per_day = models.IntegerField(default=0)
It seems you are using a ModelForm to grab the data from the user.
In this case, the solution proposed by sasuke will not work. First, you would have to set the required param to False in your form fields, so you would stop seing those "This field is required" messages. Still, you would see errors when saving the form. Even if your model instance is initialized with the default value, the form will replace it with None, since there is an existing field in the form matching the field in the model and its value is None.
My solution is to override the values in the model instance before saving them:
model_instance = myform.save(commit=False)
if not model_instance.not_before:
model_instance.not_before = '00:00:00'
if not model_instance.max_num_per_day:
model_instance.max_num_per_day = 0
model_instance.save()

How to make an auto-filled and auto-incrementing field in django admin

[Update: Changed question title to be more specific]
Sorry if I didn't make the question very well, I can't figure how to do this:
class WhatEver():
number = model.IntegerField('Just a Field', default=callablefunction)
...
Where callablefunction does this query:
from myproject.app.models import WhatEver
def callablefunction():
no = WhatEver.objects.count()
return no + 1
I want to automatically write the next number, and I don't know how to do it.
I have errors from callablefunction stating that it cannot import the model, and I think there must be an easier way to do this. There's no need even to use this, but I can't figure how to do it with the pk number.
I've googled about this and the only thing I found was to use the save() method for auto incrementing the number... but I wanted to show it in the <textfield> before saving...
What would you do?
Got it! I hope this will help everyone that has any problems making a auto-filled and auto-incrementing field in django. The solution is:
class Cliente(models.Model):
"""This is the client data model, it holds all client information. This
docstring has to be improved."""
def number():
no = Cliente.objects.count()
if no == None:
return 1
else:
return no + 1
clientcode = models.IntegerField(_('Code'), max_length=6, unique=True, \
default=number)
[... here goes the rest of your model ...]
Take in care:
The number function doesn't take any arguments (not even self)
It's written BEFORE everything in the model
This was tested on django 1.2.1
This function will automatically fill the clientcode field with the next number (i.e. If you have 132 clients, when you add the next one the field will be filled with clientcode number 133)
I know that this is absurd for most of the practical situations, since the PK number is also auto-incrementing, but there's no way to autofill or take a practical use for it inside the django admin.
[update: as I stated in my comment, there's a way to use the primary key for this, but it will not fill the field before saving]
Every Django model already has an auto-generated primary key:
id = models.AutoField(primary_key=True)
It seems you are trying to duplicate an already existing behavior, just use the object primary key.
I, too, came across this problem, my instance of it was customer.number which was relative to the customers Store. I was tempted to use something like:
# Don't do this:
class Customer(models.Model):
# store = ...
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
try:
self.number = self.store.customer_set.count() + 1
else:
self.number = 1
super(Customer, self).save(*args, **kwargs)
The above can cause several problems: Say there were 10 Customers, and I deleted customer number 6. The next customer to be added would be (seemingly) the 10th customer, which would then become a second Customer #10. (This could cause big errors in get() querysets)
What I ended up with was something like:
class Store(models.Model):
customer_number = models.IntegerField(default=1)
class Customer(models.Model):
store = models.ForeignKey(Store)
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
self.number = self.store.customer_number
self.store.number += 1
self.store.save()
super(Customer, self).save(*args, **kwargs)
PS:
You threw out several times that you wanted this field filled in "before". I imagine you wanted it filled in before saving so that you can access it. To that I would say: this method allows you to access store.customer_number to see the next number to come.
You have errors in code, that's why you can't import it:
from django.db import models
class WhatEver(models.Model):
number = models.IntegerField('Just a Field', default=0)
and Yuval A is right about auto-incrementing: you don't even need to declare such a field. Just use the pk or id, they mean the same unless there's a composite pk in the model:
> w = Whatever(number=10)
> w
<Whatever object>
> w.id
None
> w.save()
> w.id
1
[update] Well, I haven't tried a callable as a default. I think if you fix these errors, it must work.

Categories

Resources