updating user profile fields django - python

Should be a simple answer but I can't figure out what's wrong here...
I have a user profile with a couple of simple fields. I'm trying to update them like so:
if data['dob'] != None:
request.user.profile.dob = data['dob']
request.user.profile.save()
This doesn't seem to have any effect at all though.
p.s. i am using a nice little trick in my UserProfile class that looks like this:
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
Could this be part of the problem?

Think about what happens in your code.
If there's a dob in your data, you call request.user.profile. This calls your property, which makes a request to the database and gets or creates a Profile instance.
Next, you call request.user.profile again. Guess what this does? Makes a fresh call to the database, and gets an instance of the Profile again. But of course this is a new instance, even though it's referring to the same database row, so it won't have the value for dob you just set on the last version.
Now, potentially you could solve this by storing the profile in a local variable:
profile = request.user.profile
profile.dob = data['dob']
profile.save()
But to be honest, I'd drop the whole hacking around with the profile property. It's going to cause you all sorts of problems.

It might be easier to use the suggested method of tying a profile to a django user:
https://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users
In the meantime, remove the [0] at the end of the UserProfile.objects.get_or_create(user=u) as that method only returns a single object regardless

Related

How can I assign a model to all users upon creation in Django?

I have this model:
models.py
class Upload_model(models.Model):
content=models.CharField(max_length=200)
user_name=models.ForeignKey(User)
forms.py
class upload_form(ModelForm):
class Meta:
model=Upload_model
fields=[“content”]
views.py
I'm trying to write a function that iterates the creation of the Upload_model to all Users independently. But it won't work.
def upload_all_user_models(request):
if request.method==”POST”:
form=upload_form(request.POST, request.FILES)
instance=form.save(commit=False)
instance.user_name=User.objects.all()
return redirect(“upload_all_user_models”)
else:
form=upload_form()
return render(request,”uploadpage.html”,{“form”:form})
I want the Upload_model to be assigned to all users upon creation but I seem to be getting a ValueError. How can I solve this?
I believe when you use instance.user_name=User.objects.all(), you're assigning the instance.user_name to a queryset of User rather than each individual users (i.e. instance.user_name=[queryset<id=1, id=2>]). You need to set up conditional statements that determine if each user doesn't have the Upload_model, then add and save Upload_model to the user.
What you're trying to create is going to be complicated due to the order in which model gets created first: the upload_model or the user.
Let's say you have 5 users and you create an Upload_model, saving the Upload_model to the 5 users. What's going to happen if user #6 comes along?
I'm sure there's a way you can configure the code to make it how you want it, but at the end of it all, your question is quite vague but all in all you're getting a ValueError because you're trying to assign user_name not to each user, but to a list of users.

Django Add list generated from the text of one field to many to many field

Having a bit of trouble trying to bulk add a list of items to a many to many field and though having tried various things have no clue on how to approach this. I've looked at the Django documentation and cant seem to find what I'm looking for.
Here is the code for my models:
class Subject(models.Model):
noun = models.CharField(max_length=30, null=True, blank=True)
class Knowledge(models.Model):
item_text = models.TextField()
item_subjects = models.ManyToManyField(Subject, null=True, blank=True)
def add_subjects(sender, instance, *args, **kwargs):
if instance.item_info:
item_subjects = classifier.predict_subjects(instance.item_info)
if item_subjects:
....
post_save.connect(add_subjects, sender=Knowledge)
The list is being generated by the classifer.predict_subjects function.
I have tried using the m2m_changed connector and the pre_save and post_save connect. I'm not even sure the many to many field is the right option would it be better to do make a foreign key relationship.
in place of the '...' I have tried this but it doesn't create the relationship between and only saves the last one.
for sub in item_subjects:
subject = Subject(id=instance.id, noun=sub)
subject.save()
I've also tried
instance.item_subjects = item_subjects
and a load more things that I can't really remember, I don't really think I'm in the right ballpark to be honest. Any suggestions?
edit:
ok, so I have got it adding all of the list items but still haven't managed to link these items to the many to many field.
for sub in item_subjects:
subject = Subject.objects.get_or_create(noun=sub)
edit 2:
So doing pretty much exactly the same thing outside of the loop in the Django shell seems to be working and saves the entry but it doesn't inside the function.
>>> k[0].item_subjects.all()
<QuerySet []>
>>> d, b = Subject.objects.get_or_create(noun="cats")
<Subject: cats>
>>> k[0].item_subjects.add(d)
>>> k[0].item_subjects.all()
<QuerySet [<Subject: cats>]>
edit 3
So I took what Robert suggested and it works in the shell just like above just not when using it in the admin interface. The print statements in my code show the array item being updated but it just dosen't persist. I read around and this seems to be a problem to do with the admin form clearing items before saving.
def sub_related_changed(sender, instance, *args, **kwargs):
print instance.item_subjects.all()
if instance.item_info:
item_subjects = classifier.predict_subjects(instance.item_info)
if item_subjects:
for sub in item_subjects:
subject, created = Subject.objects.get_or_create(noun=sub)
instance.item_subjects.add(subject)
print instance.item_subjects.all()
post_save.connect(sub_related_changed, sender=Knowledge)
I have tried using the function as m2m_changed signal as follows:
m2m_changed.connect(model_saved, sender=Knowledge.item_subjects.through)
But this either generates a recursive loop or doesn't fire.
Once you have the subject objects (as you have in your edit), you can add them with
for sub in item_subjects:
subject, created = Subject.objects.get_or_create(noun=sub)
instance.item_subjects.add(subject)
The "item_subjects" attribute is a way of managing the related items. The through relationships are created via the "add" method.
Once you've done this, you can do things like instance.item_subjects.filter(noun='foo') or instance.item_subjects.all().delete() and so on
Documentation Reference: https://docs.djangoproject.com/en/1.11/topics/db/examples/many_to_many/
EDIT
Ahh I didn't realize that this was taking place in the Django Admin. I think you're right that that's the issue. Upon save, the admin calls two methods: The first is model_save() which calls the model's save() method (where I assume this code lives). The second method it calls is "save_related" which first clears out ManyToMany relationships and then saves them based on the submitted form data. In your case, there is no valid form data because you're creating the objeccts on save.
If you put the relevant parts of this code into the save_related() method of the admin, the changes should persist.
I can be more specific about where it should go if you'll post both your < app >/models.py and your < app >/admin.py files.
Reference from another SO question:
Issue with ManyToMany Relationships not updating inmediatly after save

Django - .delete() method is called, but instance remains in DB

got a little problem here using the Django-framework and its
.delete() method.
Im using the following model
class Message(models.Model):
...
sender = models.ForeignKey(User, related_name="%(class)s_sender")
user = models.ManyToManyField(User, related_name="%(class)s_recip")
trash = models.ManyToManyField(User, related_name="%(class)s_trash", blank=True)
...
So my aim is to check if both M2M fields "user" and "trash" do not contain objects/keys anymore, and if so, the corresponding Message instance should be deleted from the database.
To archieve this I'm using m2m_changed signals as follows
def set_recip_names(sender, instance, action,**kwargs):
print instance
if action == "post_remove":
if instance.user.all().count() == 0 and instance.trash.all().count() == 0:
print instance.id
instance.delete()
def msg_del(sender, **kwargs):
print "Message deleted"
m2m_changed.connect(set_recip_names, sender=Message.user.through)
m2m_changed.connect(set_recip_names, sender=Message.trash.through)
post_delete.connect(msg_del, sender=Message)
The first two print statements are for debug purpous of course, and both print exactly what Im expecting: instance IS the corresponding instance to be deleted, and instance.id confirms this, as well as telling me that Django actually will execute the .delete() method.
The second post_delete signal is for debugging purpous too, and it is called as expected, so Im getting the output "Message deleted".
So the big problem is, that the "to-be-deleted" instance remains in the database. Ive searched the internet for an hour now but I dont see anything is wrong with my code.
Any suggestions are welcomed, or if you know any better way to do what Im trying to do, please let me know.
Solved
Problem was actually pretty obvious. One of my code snippets, where on user interaction elements from my M2M fields are removed looks like
...
try:
message = Message.objects.get(id=msg)
if usr not in message.trash.all():
continue
if usr != message.sender:
message.user.remove(usr) # m2m_changed triggered
message.trash.remove(usr) # m2m_changed triggered
# if instance was removed by now, this .save() will
# insert the python-local object in the db, so it looked
# like the same instance is still there, but I saw that
# the "old" instance suddenly had a new id
message.save()
except Message.DoesNotExist:
continue
...
You can delete the record directly by doing this:
YourModel.objects.get(id=instance.id).delete()
If the record exists, then it will be deleted in the database.
Your instance is saved in the RAM and may not be deleted, you can do that manually by deleting it from the RAM: del instance

Django: removing m2m relation in object

I just started playing with Django, I love it! I am still working my way around with the Django ORM though...
At the moment I have a model Shift with a m2m relationship with users:
class Shift(models.Model):
users = models.ManyToManyField(User)
I want to define a view that checks if a M2M relationship exists, if it does, it removes this relationship. This is where I am stuck: I am able to lookup if a relationship exists, but I am not able to remove it. What is wrong with my code?
def remove_if_exists(request, shift_id, username):
shift = get_object_or_404(Shift, pk=shift_id)
if shift.users.filter(username=username).exists()
shift.users.remove(username)
The trouble with your code is that the relationship is not with a username, but with a User object. So your call to remove should have a User object as its argument. You need to actually get the relevant user from the db first, then call remove with that object.
However, there is a shortcut: remove does not raise an error if the object is not in the related set, so you can skip the exists call. That gives just:
user = User.objects.get(username=username)
shift = get_object_or_404(Shift, pk=shift_id)
shift.users.remove(user)
Some stupid syntax mistake of my own, should be:
shift.users.remove(User.objects.get(username=username))

I came across a tricky trouble about django Queryset

Tricky code:
user = User.objects.filter(id=123)
user[0].last_name = 'foo'
user[0].save() # Cannot be saved.
id(user[0]) # 32131
id(user[0]) # 44232 ( different )
user cannot be saved in this way.
Normal code:
user = User.objects.filter(id=123)
if user:
user[0].last_name = 'foo'
user[0].save() # Saved successfully.
id(user[0]) # 32131
id(user[0]) # 32131 ( same )
So, what is the problem?
In first variant your user queryset isn't evaluated yet. So every time you write user[0] ORM makes independent query to DB. In second variation queryset is evalutaed and acts like normal Python list.
And BTW if you want just one row, use get:
user = User.objects.get(id=123)
when you index into a queryset, django fetches the data (or looks in its cache) and creates a model instance for you. as you discovered with id(), each call creates a new instance. so while you can set the properties on these qs[0].last_name = 'foo', the subsequent call to qs[0].save() creates a new instance (with the original last_name) and saves that
i'm guessing your particular issue has to do with when django caches query results. when you are just indexing into the qs, nothing gets cached, but your call if users causes the entire (original) qs to be evaluated, and thus cached. so in that case each call to [0] retrieves the same model instance
Saving is possible, but everytime you access user[0], you actually get it from the database so it's unchanged.
Indeed, when you slice a Queryset, Django issues a SELECT ... FROM ... OFFSET ... LIMIT ... query to your database.
A Queryset is not a list, so if you want to it to behave like a list, you need to evaluate it, to do so, call list() on it.
user = list(User.objects.filter(id=123))
In your second example, calling if user will actually evaluate the queryset (get it from the database into your python program), so you then work with your Queryset's internal cache.
Alternatively, you can use u = user[0], edit that and then save, which will work.
Finally, you should actually be calling Queryset.get, not filter here, since you're using the unique key.

Categories

Resources