Django - Create related object on first call - python

I have some models:
class User(AbstractUser):
cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True)
class Cart(models.Model):
products = models.ManyToManyField('product.Product')
date_updated = models.DateTimeField(auto_now=True, editable=False)
Now I need to create user cart instance at first call.
if request.user.cart is None:
request.user.cart = Cart.objects.create()
request.user.save()
This method is not good for me, because it leads to code duplication (every time I need to import Cart model and check the user's cart is it None or not).
The best way that I can find is AutoOneToOneField in django-annoying, but unfortunately it absolutely broke current field autocomplete in PyCharm.
What is the right way to do that?
P.S. I really don't need to create user cart object at user creation moment.
P.P.S. Sorry for my bad English.
UPDATE: Thank you very much! I came to the first code fragment on my own, but it is noob edition script:
class User(AbstractUser):
_cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')
#property
def cart(self):
return self._cart # just for test
...end in my view i've got error like "object Cart has no property 'products'". But next code works great
class User(AbstractUser):
_cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')
#property
def cart(self):
if not self._cart:
self._cart = Cart.objects.create()
self.save(update_fields=('_cart',))
return self._cart
...except "unresolved attribute reference" warning in view:
if request.user.cart.add(type, pk):
messages.success(request, 'Added to cart')
but anyway, that warning should be PyCharm bug (do not autocomplete any #property-decorated methods). You are great, thank's a lot!

There's a couple different ways I can think of off the cuff:
A middleware that does the check on every request – clean, but causes extra database hits even if that request doesn't need the cart on the user
A separate function get_cart(request) -> Cart that does the check
An accessor on your custom User:
class User(AbstractUser):
_cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')
#property
def cart(self):
if not self._cart_id:
self._cart = ...
self.save(update_fields=('_cart',))
return self._cart
Also, you might want to consider
class Cart(models.Model):
user = models.OneToOneField(User)
products = models.ManyToManyField('product.Product')
date_updated = models.DateTimeField(auto_now=True, editable=False)
# and
class User(AbstractUser):
#cached_property
def cart(self):
cart, created = Cart.objects.get_or_create(user=self)
return cart
instead.

Related

TypeError: Field 'id' expected a number but got <django.contrib.auth.models.AnonymousUser object at 0x048C7E90>

Cannot cast AnonymousUser to int.
Are you trying to use it in place of User?
I am unsure how to adjust my code to stop generating this error when i make a post request to my url. I have attached code for my serializers, views and models.
class ActivitySessionSerializer(serializers.Serializer):
activity_name = serializers.CharField(min_length=1, max_length=100)
def create(self, validated_data):
activity, created = Activity.objects.get_or_create(name=validated_data['activity_name'],
owner=self.context['request'].user)
return ActivitySession.objects.create(activity=activity, start_time=datetime.datetime.now())
class StartSessionView(generics.CreateAPIView):
serializer_class = serializers.ActivitySessionSerializer
User = get_user_model()
class ActivityType(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Activity(models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
activity_type = models.ForeignKey(ActivityType, on_delete=models.CASCADE, null=True, blank=True)
class ActivitySession(models.Model):
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
urlpatterns = [
path('starting_session/', views.StartSessionView.as_view()),
]
I believe i solved it by changing line
Activity.objects.get_or_create(name=validated_data['activity_name'],
owner=self.context['request'].user)
to
Activity.objects.get_or_create(name=validated_data['activity_name'],
owner=self.context['request'].user.id)
I had to add id field specifically instead of the total user object
just need to add request.user.id /// this will solve for sure..
AnonymousUser has id=None and pk=None, so is not directly useable in this situation. Instead, the recommendation is to create an instance of user that reflects an anonymous user.
For those who also come here, one possible bug is (and this is what I encountered):
you might call the anonymous user instance somewhere else through foreignkey or reverse foreignkey.
For example, in my code:
I received the anonymous request in my CustomView like this:
class CustomView(RetrieveAPIView):
serializer_class = CustomSerializer
queryset =CustomModel.objects.all()
The CustomModel of the CustomView has a reverse foreignkey-which_model, like this:
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomModel(BaseModel):
name = models.CharField(verbose_name="Custom name", max_length=256)
class CustomRecord(BaseModel):
which_user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="related user")
which_model = models.ForeignKey("CustomModel", on_delete=models.CASCADE, verbose_name="related model",related_name="user_record",)
To get the related CustomRecord instance simultaneously when getting the CustomModel instance in the anonymous request, I used the SerializerMethodField in the CustomSerializer like this:
class CustomSerializer(ModelSerializer):
related_user_record = SerializerMethodField(read_only=True, source="user_record")
def get_related_user_record(self, obj):
# This is where the problem is !!!!
user_record = CustomRecord.objects.filter(
which_model=obj.id, which_user=self.context["request"].user
)
if not user_record:
return None
else:
return CustomRecordSerializer(user_record.get()).data
Because I used the self.context["request"].user in get_related_user_record function, django try to convert the id of user instance to int;
However, like #Brad Solomon said, the anonymous user's id or pk is None., that's where the error comes from. And honestly, djano didn't do well in reporting the bug source when this error happens.
What I did to sovle this problem is that I added a check before using the user instance, to see if it's an anonymous user, like this:
def get_related_user_record(self, obj):
# check if it's an anonymous user
if not self.context["request"].user.pk:
return None
# This is where the problem is !!!!
user_record = CustomRecord.objects.filter(
which_model=obj.id, which_user=self.context["request"].user
)
if not user_record:
return None
else:
return CustomRecordSerializer(user_record.get()).data

Is it possible to create a new row in models.py for a list of items in django?

I'm trying to create a todo app, and I want to have a CharField in my models.py for each todo item. Here's my code right now:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
todo = models.CharField(max_length=1000, default='')
def __str__(self):
return f'{self.user.username} Profile'
I want to make it so a new row in the database is created for each new todo. For example, there would be a row for one task, and another row for another task. How would I go about this? Thanks in advance!
Add additional model(table) for ToDo and connect it with many-to-one relationship
Class ToDo(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
todo = models.CharField(max_length=1000, default='')
You should probably want to read a bit more about database relationships

Django model for voting system and follow feature

I work on the voting system (vote up and vote down) and the functionality - follow.
I want it to be done well, because I don't have anyone to advise, I put the post and code here.
Follow function - it should show how many followers there are and who they are. I used here a m2m relation with the intermediate model Follower.
My question - is this the correct approach to the topic - using m2m with an intermediate model here?
Functionality vote up and vote down - it is supposed to show how many votes up and how many down and who voted down and who voted up.
My question is whether there is also OK here with the relation between m2m and the intermediate model Voter?
Code for follow feature:
class Post(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=255, unique=True)
description = models.TextField(max_length=1024)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Follower', blank=True)
is_visible = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('posts:post_detail', kwargs={'pk': self.pk})
def number_of_followers(self):
return self.followers.count()
class Follower(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user
Code for vote up and vote down feature:
class Question(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
vote_up = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Voter', blank=True)
vote_down = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Voter', blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('qa:qa_detail', kwargs={'pk': self.id})
class Voter(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user
Now is working only follow feature but I want to make sure my approch is ok. Please and thanks for your help.
Currently there is nothing that differentiates the up_vote from the down_vote on the Question model so this will return the same query.
As a side note if you plan to add similar voting/following functionality to other models it may be worth considering whether this is a good use case for a Generic Relationship. This will create a polymorphic relationship and is DRY.
Here are the docs
generic relations

Django follow feature with m2m

I tried to solve the problem and got stuck. The problem is that I have a post that I can follow. My problem is that I don't know how to add a tracking button. Should this be done by url, with a view? Or should it be rather as a method in the model?
My problem is also whether it is properly written in terms of models - using the intermediate model Follower?
Here is Post model and I would like to add followers here. I mean, everybody who is interested, can follow this post.
class Post(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=255, unique=True)
description = models.TextField(max_length=1024)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Follower', blank=True)
is_visible = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('posts:post_detail', kwargs={'pk': self.pk})
def number_of_followers(self):
return self.followers.count()
Here is my manager for follower model:
class FollowerManager(models.Manager):
use_for_related_fields = True
def follow(self, user, pk):
post_object = get_object_or_404(Post, pk=pk)
if user.is_authenticated():
if user in post_object.followers.all():
Follower.objects.filter(post=post_object, user=user).delete()
else:
Follower.objects.create(post=post_object, user=user)
Here is Follower model:
class Follower(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
objects = FollowerManager()
Interactions between a user's browser and the database can only be done via a URL and a view. That view might call a model method, but there is no possible way for the browser to call that method directly.
(Also I don't understand what you're doing in the manager. Why are you deleting followers if the user is authenticated? Note that will always be true, so the followers will always be deleted.)

Ordering mptt tree using a field

This is the original category model:
class Category(MPTTModel):
name = models.CharField(max_length=50, unique=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
def __unicode__(self):
return self.name
class MPTTMeta:
order_insertion_by = ['name']
Then I needed to order the category, so I altered it like the following:
class Category(MPTTModel):
name = models.CharField(max_length=50, unique=True)
order = models.SmallIntegerField() <<<<<<<<<
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
def __unicode__(self):
return self.name
class MPTTMeta:
order_insertion_by = ['order'] <<<<<<<<<
And I changed the Django admin declaration from:
admin.site.register(Category, MPTTModelAdmin)
To:
class CategoryAdmin(MPTTModelAdmin):
list_display = ('name', 'order')
list_editable = ('order',)
admin.site.register(Category, CategoryAdmin)
Then everything fall apart after making a few edits from the admin control panel. I can't describe exactly what happened but it seems the lft, rght, level and parent_id where messed up by these changes.
Am I using order_insertion_by in the wrong context? Is it for something else? I tried to search the docs but didn't get a useful answer.
I faced this problem. The problem is not in the package django-mptt, and in the framework Django, more precisely in the admin. Perhaps this is due to several administrators working simultaneously. While only one solution - to abandon the list_editable in admin class or write a script with the same field order for Ajax.
In order to restore the tree, use rebuld method: Category.tree.rebuild()

Categories

Resources