Modify value in data table UserProfile using function - python

I am trying to run .save() to change the value of a user model field.
Here is my code:
Views.py:
def traffic_task(request):
tasks_traffic = Task.objects.filter(category="traffic")
random_task = random.choice(tasks_traffic)
task_id = random_task.pk
user = request.user
user.userprofile.daily_task = task_id
user.save()
return task_detail(request=request, pk=task_id)
Models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
daily_task = models.IntegerField(default=0)
daily_task_done = models.BooleanField(default=False)
daily_task_done_time = models.DateTimeField(default=datetime.now() - timedelta(days=2))
They are in two different apps so maybe it's an import missing?

You should save the UserProfile object, not the User object, so something like:
def traffic_task(request):
tasks_traffic = Task.objects.filter(category="traffic")
random_task = random.choice(tasks_traffic)
task_id = random_task.pk
userprofile = request.user.userprofile
userprofile.daily_task = task_id
# perhaps you want to set daily_task_done to False here
userprofile.save()
return task_detail(request=request, pk=task_id)
Furthermore based on the code you provide, it looks like you want to add a ForeignKey to Task, it is better not to save the value of the primary key, since the FOREIGN KEY constraints, etc. are not enforced:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
daily_task = models.ForeignKey('someapp.Task', null=True, default=None)
daily_task_done = models.BooleanField(default=False)
daily_task_done_time = models.DateTimeField(default=datetime.now() - timedelta(days=2))
Then you can use a Task object, like:
def traffic_task(request):
tasks_traffic = Task.objects.filter(category="traffic")
random_task = random.choice(tasks_traffic)
userprofile = request.user.userprofile
userprofile.daily_task = random_task
userprofile.save()
return task_detail(request=request, pk=task_id)
This thus creates extra validation, but it is also more convenient to work with the Task object, and in case you want to obtain the Tasks "in bulk", one can use .select_related(..), or .prefetch_related(..) (although one can do this with an IntegerField as well, it will require extra logic, and thus is less elegant).

Related

Django: how to use .filter( ) method in django?

I am trying to display quiz only for users that are registered in a particular course, i.e if a user is registered in a Frontend Crash Course i want them to see only the quiz related to that course they are registered in, and not all the quiz from the db.
i have a model UserCourse where i am storing all the courses a user have enrolled in, when i try filtering by that models while user_course is get like this below
user_course = UserCourse.objects.get(user=request.user)
quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
i get this error get() returned more than one UserCourse -- it returned 3! Now i have changed .get() to .filter() like this
user_course = UserCourse.objects.filter(user=request.user)
quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
i then get this error The QuerySet value for an exact lookup must be limited to one result using slicing.
What is the right way to write this query.
models.py
class UserCourse(models.Model):
user = models.ForeignKey(User , null = False , on_delete=models.CASCADE)
course = models.ForeignKey(Course , null = False , on_delete=models.CASCADE, related_name="usercourse")
class Quiz(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quizzes")
title = models.CharField(max_length=255)
course = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True, related_name="quizzes")
date = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(unique=True)
user_course = models.ForeignKey(UserCourse, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
The Problem in the Second Line
user_course = UserCourse.objects.filter(user=request.user)
quizzes=Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
remember that when You are using filter you get QuerySet not one object
if you want to return the quizes those related to user_course_queryset you can use __in filter
print(user_course) # print it to understand more
quizzes=Quiz.objects.filter(course__usercourse__in=user_course)
this will Return every Quiz Related to the QuerySet objects

Checking if relationship exists with query

I am trying to check whether or not a following relationship exists using a query. First, I get all of the followers the user has and then I check whether or not the user follows those followers. Here are my models:
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)
class User(AbstractBaseUser):
username = models.CharField(max_length=15, unique=True)
email = models.EmailField(max_length=100, unique=True)
I am using the Django Rest-Framework so I go to the specific URL to get the information I need. After going to the URL, the output is expected. I get all the followers the user has.
views.py
class GetFollowersView(ListAPIView):
serializer_class = FollowingSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return User.objects.filter(targets__target=requested_user).order_by('-targets__created_at'). \
annotate(is_following=Count('followers__follower', filter=Q(followers__follower=requested_user), distinct=True))
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class FollowingSerializer(serializers.ModelSerializer):
is_following = serializers.IntegerField()
class Meta:
model = User
fields = ('id', 'username', 'follower_count', 'following_count', 'is_following')
However, the problem is in the is_following annotation. I'd like to see whether or not the user follows each specific follower. If they follow that follower, then is_following should be 1 if not, then it is a 0. I'm getting incorrect results in is_following is there a way I can check if the user follows each specific follower?
If you have Django Debug Toolbar installed and you check the query for your current filter/annotate, this is what it shows (for a single user)
SELECT "user"."id", "user"."username", "user"."email",
COUNT(DISTINCT T4."follower_id") AS "is_following" FROM "user"
INNER JOIN "following" ON ( "user"."id" = "following"."follower_id" )
LEFT OUTER JOIN "following" T4 ON ( "user"."id" = T4."target_id" )
WHERE "following"."target_id" = 4 GROUP BY "user"."id", "user"."username",
"user"."email", "following"."created_at" ORDER BY "following"."created_at"
DESC
However to get the count of the users the chosen user follows, you really want something like this
SELECT ue."id", ue."username", ue."email", COUNT(DISTINCT fe."target_id") AS
"is_following" FROM "user" u inner JOIN "following" fe ON ( u."id" =
fe."follower_id" ) inner join user ue on fe.target_id = ue.id and u.id = 4
GROUP BY ue."id", ue."username", ue."email"
I don't think it is possible to combine both the followers and the followee in the same query like you have done. You could possibly find the intersection and then proceed from there...Something like this..
def get_queryset(self):
username = self.request.query_params.get('username', None)
requested_user = models.User.objects.get(username=username)
following_me = models.User.objects.filter(targets__target=requested_user).order_by('-targets__created_at')
i_follow = models.User.objects.filter(followers__follower=requested_user).order_by('-followers__created_at')
common = following_me & i_follow
### Set is_following for common as 1, all others as 0.
#......
#......
return following_me
Why not use an M2M relationship? Seems like this could be simple:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=200)
followers = models.ManyToManyField('User')
#property
def follower_count(self):
# How many people follow me
return len(self.followers)
#property
def followee_count(self):
# How many people I follow
return len(self.user_set.all())
And you can modify the get_queryset() to only find followers:
User.objects.filter(followers__contains=self.request.user)
Does this help?

django duplicates in pagination with order_by() on manytomany field

I am sorting a query on a timefield from a manytomany object but have trouble during pagination.
models.py:
class Validated(models.Model):
job_id_list = models.ManyToManyField(JobId , related_name='JobId', blank=True)
class JobId(models.Model):
user = models.ForeignKey(User, blank=True, null=True, default=None)
job_time = models.DateTimeField(auto_now_add=True)
views.py:
results_list = Validated.objects.filter(job_id_list__user=request.user).\
distinct().order_by('-job_id_list__job_time')
My problem is that pagination is messed up and I get duplicates in my pagination output, even when the query output is ok. (other have this problem too)
I tried several solutions but my issue does not get solved as sorting is bases on a field from a many-to-many object order_by('job_id_list__job_time')
I was thinking of a work-around through creating an annotation to my model by the use of a Model.Manager. Inside this annotation, i try to add the job_time which is a result of a function inside the model. In that way, the job_time would be easily accessible:
for i in result_list:
job_time_from_model = result_list[i].job_time
I would do this as followed but I don't know how to incorporate the %%function logic%% inside the annotation.
Is this even possible in this way or is another approach required?
models.py:
class ValidatedManager(models.Manager):
**%%function-logic%%**
def date(self, user, id):
xlinkdatabase_validated_object = self.get(id=id)
job_id_list = xlinkdatabase_validated_object.\
job_id_list.all().order_by('-job_time')
date_added = None
for item in job_id_list:
if item.user == user:
date_added = item.job_time
break
return date_added
def get_queryset2(self, user):
qs = super(XlinkdatabaseValidatedManager, self).\
get_queryset().annotate(job_date= **%%function-logic%%**, output_field=DateTimeField())
return qs
views.py:
results_list = xlinkdatabase_validated.objects.get_queryset2(request.user).\
filter(job_id_list__user=request.user).distinct().order_by('-job_date')

Selecting data from few relational tables based on few conditions in Django

I'm using Django 1.6 with Python 2.7 and I have few related models:
# keys/models.py
class Key(models.Model):
user = models.ForeignKey('auth.User')
is_valid = models.BooleanField()
# entities/models.py
class Entity(models.Model):
user = models.ForeignKey('auth.User')
key = models.ForeignKey('keys.Key')
active = models.BooleanField(default=False)
# profile/models.py
class Profile(models.Model):
user = models.ForeignKey('auth.User')
profile_id = models.PositiveIntegerField(null=True, blank=True)
Is it possible to make a single-line query which would check these conditions:
Key.is_valid must be True
Entity.active must be True
Profile.profile_id must not be null (or None)
The only thing I can pass to that query is request.user.
if you are wanting to get Entity objects:
objects = Entity.objects.filter(active=True,
key__is_valid=True,
user__profile__profile_id__isnull=False)
I think that this is what you need:
Check entity:
entity = Entity.objects.filter(active=True, key__is_valid=True, user=request.user)
Check Profile
profile = Profile.objects.filter(user=request.user, profile_id__isnull=False)

Django-powered library checkout system

I am working on a library system to manage certain items in our office, I don't need a full-blown integrated library system so I decided to hand roll one with Django.
Below is a simplified version of my model:
class ItemObjects(models.Model):
# Static Variables
IN_STATUS = 'Available'
OUT_STATUS = 'Checked out'
MISSING = 'Missing'
STATUS_CHOICES = (
(IN_STATUS, 'Available'),
(OUT_STATUS, 'Checked out'),
(MISSING, 'Missing'),
)
# Fields
slug = models.SlugField(unique=True)
date_added = models.DateField(auto_now_add=True)
last_checkin = models.DateTimeField(editable=False, null=True)
last_checkout = models.DateTimeField(editable=False, null=True)
last_activity = models.DateTimeField(editable=False, null=True)
status = models.CharField(choices=STATUS_CHOICES, default=IN_STATUS, max_length=25)
who_has = models.OneToOneField(User, blank=True, null=True)
times_out = models.PositiveIntegerField(default=0, editable=False)
notes = models.CharField(blank=True, max_length=500)
history = models.TextField(blank=True, editable=False)
pending_checkin = models.BooleanField(default=False)
pending_transfer = models.BooleanField(default=False)
At first I was using a method on ItemObject to process checking out an item to a user and who_has was an EmailField because I couldn't get a CharfField to populate with the logged in user's name, but I figured using a OneToOneField is probably closer to the "right" way to do this.. While who_has was an EmailField, the following method worked:
def check_out_itemobject(self, user):
user_profile = user.get_profile()
if self.status == 'Available' and self.who_has == '':
self.status = 'Checked out'
self.who_has = user.email
self.last_checkout = datetime.datetime.now()
self.last_activity = datetime.datetime.now()
self.times_out += 1
if self.history == '':
self.history += "%s" % user_profile.full_name
else:
self.history += ", %s" % user_profile.full_name
if user_profile.history == '':
user_profile.history += self.title
else:
user_profile.history += ", %s" % self.title
else:
return False # Not sure is this is "right"
user_profile.save()
super(ItemObjects, self).save()
Now that I am using a OneToOneField this doesn't work, so I started looking at using a subclass of ModelForm but none of the cases I saw here on SO seemed to apply for what I am trying to do; my form would be a button, and that's it. Here are some of the questions I looked at:
Django: saving multiple modelforms simultaneously (complex case)
(Django) (Foreign Key Issues) model.person_id May not be NULL
django update modelform
So was I on the right track with a sort of altered save() method, or would a ModelForm subclass be the way to go?
EDIT/UPDATE: Many thanks to #ChrisPratt!
So I am trying to get Chris Pratt's suggestion for showing ItemHistory to work, but when I try to render it on a page I get an AttributeError that states "'User' object has no attribute 'timestamp'". So my question is, why is it complaining about a User object when last_activity is an attribute on the ItemObject object ?
My view:
#login_required
def item_detail(request, slug):
item = get_object_or_404(Item, slug=slug)
i_history = item.last_activity
user = request.user
return render_to_response('items/item_detail.html',
{ 'item' : item,
'i_history': i_history,
'user' : user })
I do not see why a User object is coming up at this point.
EDIT2: Nevermind, history is clearly a M2M field whose target is User. That's why!
Assuming users will log in and check out books to themselves, then what you most likely want is a ForeignKey to User. A book will only have one User at any given time, but presumably Users could check out other items as well. If there is some limit, even if the limit is actually one per user, it would be better to validate this in the model's clean method. Something like:
def clean(self):
if self.who_has and self.who_has.itemobject_set.count() >= LIMIT:
raise ValidationError('You have already checked out your maximum amount of items.')
Now, you checkout method has a number of issues. First, status should be a defined set of choices, not just random strings.
class ItemObject(models.Model):
AVAILABLE = 1
CHECKED_OUT = 2
STATUS_CHOICES = (
(AVAILABLE, 'Available'),
(CHECKED_OUT, 'Checked Out'),
)
...
status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=AVAILABLE)
Then, you can run your checks like:
if self.status == self.STATUS_AVAILABLE:
self.status = self.STATUS_CHECKED_OUT
You could use strings and a CharField instead if you like, as well. The key is to decouple the static text from your code, which allows much greater flexibility in your app going forward.
Next, history needs to be a ManyToManyField. Right now, your "history" is only who last checked the item out or what the last item the user checked out was, and as a result is pretty useless.
class ItemObject(models.Model):
...
history = models.ManyToManyField(User, through='ItemHistory', related_name='item_history', blank=True)
class ItemHistory(models.Model):
CHECKED_OUT = 1
RETURNED = 2
ACTIVITY_CHOICES = (
(CHECKED_OUT, 'Checked Out'),
(RETURNED, 'Returned'),
)
item = models.ForeignKey(ItemObject)
user = models.ForeignKey(User)
activity = models.PostiveIntegerField(choices=ACTIVITY_CHOICES)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-timestamp'] # latest first
Which then allows you to get full histories:
some_item.history.all()
some_user.item_history.all()
To add a new history, you would do:
ItemHistory.objects.create(item=some_item, user=some_user, activity=ItemHistory.CHECKED_OUT)
The auto_now_add attribute ensures that the timestamp is automatically set when the relationship is created.
You could then actually get rid of the last_checkout and last_activity fields entirely and use something like the following:
class ItemObject(models.Model):
...
def _last_checkout(self):
try:
return self.history.filter(activity=ItemHistory.CHECKED_OUT)[0].timestamp
except IndexError:
return None
last_checkout = property(_last_checkout)
def _last_activity(self):
try:
return self.history.all()[0].timestamp
except IndexError:
return None
last_activity = property(_last_activity)
And, you can then use them as normal:
some_item.last_checkout
Finally, your checkout method is not an override of save so it's not appropriate to call super(ItemObject, self).save(). Just use self.save() instead.

Categories