I need some conceptual help understanding how this idea would work. If I have a user make a list and they can add infinite items to each list, how would I want to map out the model?
In my list model I have user = ForeignKey(User), so each instantiation of this list model is attributed to the user currently logged in and they can have a series of lists. However, if they want to add an item to the list, would I have a new model called add_item and make the list_name = foreignkey(user.listname)?
#New List Model
class NewList(models.Model):
user = models.ForeignKey(User)
list_name = models.CharField(max_length = 100, default = "Enter List Name")
picture = models.ImageField(upload_to='profile_images', blank=True)
def __unicode__(self):
return self.list_name
#New Item Model
class NewItem(models.Model):
lists = models.ForeignKey(NewList)
# list_name = models.OneToOneField(User.list_name)
def __unicode__(self):
return self.user.username
If I understands right,
class MyList(models.Model):
user = ForeignKey(User)
list_name = CharField(...)
....
def list_items(self):
return self.mylistitems_set.all()
class MyListItem(models.Model):
mylist = ForeignKey(MyList)
item_name = .....
....
A user may create as many lists an he/she wants and may add any number of items to a specific list. So logic is you create any number of lists (a record on MyList) and add as many items as you want to it through MyListItem.
Following is a good way to do this:
alist = MyList(......)
alist.mylistitem_set.create(...) # add an item to alist
Django Reverse relations documentation is here
Update: Taking all lists of a specific user
user = User.objects.get() # a specific user
user.mylist_set.all() # returns all lists belong to that user.
You have to read more about reverse relations in django.
Sounds about right. If your model is eg named MyList, then:
my_user = ... # obtain a user object
my_item = MyList(user=my_user)
my_item.save()
Related
I have three models which are related to each other. Now I want to query a set of Poller entries which is filtered by the categories_selected by the user that stores strings of poller_category.
# Models
class Category(models.Model):
"""
Holds all available Categories
"""
category = models.CharField(max_length=30)
class UserCategoryFilter(models.Model):
"""
Holds Categories selected by user
"""
user = models.ForeignKey(Account, on_delete=models.CASCADE)
categories_selected = models.CharField(max_length=2000)
class Poller(models.Model):
"""
Holds Poller objects
"""
poller_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
poller_category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
# View
def render_random_poller(request):
# Get user category filter if logged in
if request.user.is_authenticated:
# Get the Users filter as a list of selections
category_filter = UserCategoryFilter.objects.filter(user=request.user).values_list('categories_selected', flat=True)
print(category_filter)
# Get a Category instance filtered by user selection of categories
category_instance = Category.objects.filter(category__in=category_filter)
print(category_instance) # returns an empty queryset !!
# Apply user's selection to the poller query and take 100 latest pollers
qs_poller = Poller.objects.filter(poller_category__category__in=category_instance).order_by('-created_on')[:100]
else:
[...]
# View that saves/updated the categories_selected
[...]
# Form validation
if filter_form.is_valid():
# Check if user already has a filter instance
instance_exists = UserCategoryFilter.objects.filter(user=request.user)
# Get the cleaned data
selection = filter_form.clean()
# Transform into json format for better data storing in Model
selection = json.dumps([c.category for c in selection['choices']])
# If not create, else update
if not instance_exists:
filter_instance = UserCategoryFilter(user=request.user,
categories_selected=selection)
filter_instance.save()
[...]
However, print(category_instance) returns an empty queryset, even though print(category_filter) returns ['category_foo']. How to get the queryset of Categories to use it as a filter for the Poller queryset?
This approach also returns an empty queryset:
category_instance = Category.objects.filter(reduce(operator.or_, (Q(category__in=x) for x in category_filter)))
Given I understand it correctly, the categories_selected is a Python list you converted to a string, and thus is a list literal.
We can parse these back to a list of objects with:
from ast import literal_eval
qs = UserCategoryFilter.objects.filter(user=request.user)
categories = [
item
for uc in qs.values_list('categories_selected', flat=True)
for item in literal_eval(uc)
]
category_instance = Category.objects.filter(category__in=categories)
I would however advise to work with a ManyToManyField [Django-doc] to link accounts to categories, like:
class UserCategoryFilter(models.Model):
# ⋮
user = models.ForeignKey(Account, on_delete=models.CASCADE)
categories_selected = models.ManyToManyField(Category)
In that case you can easily filter with:
Category.objects.filter(usercategoryfilter__user=request.user)
Django can thus make JOINs over many-to-many relations to move work from the Django/Python layer to the database layer.
so I have 4 models
class User(models.Model):
userID = models.CharField(pk = True)
......
class Producer(models.Model):
userID = models.OneToOneField('Users.User',on_delete=CASCADE,primary_key=True)
.....
class Buyer(models.Model):
userID = models.OneToOneField('Users.User',on_delete=CASCADE,primary_key=True)
.....
class Inventory(models.Model):
item_id = models.UUIDField(primary_key=True,auto_created=True,default=uuid.uuid4)
producerID = models.ForeignKey('Producers.Producer',on_delete=CASCADE)
.....
class Cart(models.Model):
userID = models.OneToOneField(Buyer, on_delete = CASCADE,primary_key = True)
last_updated = models.DateTimeField(auto_now = True)
class Cart_Item(models.Model):
cart_item_id = models.UUIDField(primary_key=True,auto_created=True,default= uuid.uuid4)
item_id = models.ForeignKey('Inventory.Inventory', on_delete= SET_NULL,null=True)
userID = models.ForeignKey(Cart,on_delete=CASCADE)
......
I then have a post-only view which processes all cart Items in order to create an order as follows
class PlaceOrderView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
user = request.user
cart = Cart_Item.objects.select_for_update().filter(userID = user).order_by('item_id__producerID')
order = {'producer':'',
'items': []
}
for item in cart:
if order['producer'] == item.values('item_id.producerID'):
order['items'].append(item)
else:
self.placeOrder(order)
order['producer'] = item.values('item_id.producerID')
order['items'] = []
order['items'].append(item)
def placeOrder(self,order):
with transaction.atomic():
#Business logic on order.
What Im trying to do is to group all cart Items by items owned by specific producers, and then place an order for that group of cart Items. Where I am having trouble is in accessing the nested field "producerID" of cart Item, which needs to be done in order to group all of my cart Items.
My placeOrder method, uses the cartItem object and so they are passed directly in the function. Currently I am serializing cart Items in the for loop just to compare the producerID's but this feels inefficient. I've read django documentation on the topic of fields, but there is not much support for nested fields. Some simple explanation on the topic would be great!
.values() is a queryset method, but since you are iterating the qs and working with each individual item you dont need it. If you have and Item you should be apple to access the fk relation
Have you tried:
order['producer'] = item.itemId.producerID
I am currently using django 1.8 and I'd like to create a more intelligent way to display information about users. Say I have something like this:
class User(models.Model):
name = models.CharField()
class Invitation(models.Model):
inviter = models.ForeignKey(User)
invitee = models.ForeignKey(User)
I want to create a field that is the unique number of user's an inviter has invited. I could see how this could be done with something like set("SELECT invitee FROM INVITATIONS WHERE inviter = 'my-user';"), but if I want this displayed in the admin panel, is there a simple way to present this?
Also, I would want this done for every user, so it feels like there is a simple way to make a field generated for every user in the table.
First, let's setup proper related_name- it'll help reduce a lot of confusion in the code.
class Invitation(models.Model):
inviter = models.ForeignKey(User, related_name="invitation_sent")
invitee = models.ForeignKey(User, related_name="invitation_recv")
With the related_name setup, we can do queries such as
user = User.objects.get(pk=1)
# retrieve all invitation objects sent by this user
user.invitation_sent.all()
# retrieve all invitation objects received by this user
user.invitation_recv.all()
Now we can actually count the number of unique invitations a user has sent out quite easily:
# count number of distinct invitee for user
user.invitation_sent.all().values('invitee').distinct().count()
Next, we can actually count the number of unique users a user has invited in a single database query for all users:
user_list = User.objects.all().annotate(
uniq_inv=Count('invitation_sent__invitee', distinct=True)
)
Each user object returned will have an additional property called uniq_inv which contains the count of unique users the user has invited
for user in user_list:
print(user.name + ' invited ' + user.uniq_inv + ' unique users')
To apply this to the admin interface, you'll need to override get_queryset method:
class MyAdmin(admin.ModelAdmin):
...
list_display = [..., 'uniq_inv']
def uniq_inv(self, obj):
return obj.uniq_inv
uniq_inv.short_description = 'Unique Invitees'
def get_queryset(self, request):
qs = super(MyAdmin, self).get_queryset(request)
qs = qs.annotate(uniq_inv=Count('invitation_sent__invitee', distinct=True))
return qs
You can use annotate, which allows to add calculated fields to a queryset.
Models:
class User(models.Model):
name = models.CharField()
class Invitation(models.Model):
inviter = models.ForeignKey(User,related_name="inviter_user")
invitee = models.ForeignKey(User,related_name="invited_user")
Queryset:
from django.db.models import Count
q = Invitation.objects.annotate(count_invitee=Count('invitee')).all()
Now "count_invitee" field has the number for each invitation object.
If you want to filter invitee from the user side.
For a single user:
User.objects.get(pk=1).invited_user.all.count()
For all users queryset:
User.objects.annotate((count_invitee=Count('invited_user')).all()
I'm not sure if my question makes sense, but here is what I'm trying to do:
I have Two Models:
class Task(models.Model):
title = models.CharField()
tasklist = models.ForeignKey('TaskList')
class TaskList(models.Model):
title = models.CharField()
users = models.ManyToManyField(User, related_name="lists")
How can I get all the tasks for a single User in one line?
Or is the code below the only solution?
user_tasks = []
user_tasklists = request.user.tasklists.all()
for list in user_tasklists:
for task in list.task_set.all():
user_tasks.append(task)
Assuming you have a user:
user = User.objects.get(pk=some_pk)
# Or if you need get the user from request:
# user = request.user
user_tasks = user.tasklist_set.all()
Now user_tasks is a queryset, so you can filter, or apply any queryset function.
To get Tasks (as shown in your last edit), you need query Task model:
Task.objects.filter(tasklist__users=user)
You can find the related objects documentation here
I'm stuck trying to figure how to do the following:
I have a few entities:
PurchaseItem (an item in user's cart),
Order (an order - combines one or many PurchaseItems),
OrderStatusHistory (that's status items for the Order - instead of changing, I create new ones to be able to retrospectively preview how status changed over time).
I don't want any of these to be created via admin - they are all created via public interface, but I have to show the Order and its attributes in the admin panel:
I need to be able to show list of orders. That's simple.
When I click on an order or something I want to be able to view the order's details:
list of Purchase items.
I need to be able to change the status of the order - selecting from a drop down or something - however, this action show be triggering a new statusHistory item creation.
Is this all possible with admin interface or should I forget about it and create my own implementation with pages and all?
My models look like this:
class Order(models.Model):
dateCreated = models.DateTimeField(null=False,default=datetime.now())
items = models.ManyToManyField(PurchaseItem)
user_name = models.CharField(null=True,blank=True,max_length=200)
phone = models.CharField(null=False,blank=False,max_length=11,validators=[validate_phone])
phone_ext = models.CharField(null=True,blank=True,max_length=5,validators=[validate_phone_ext])
email = models.CharField(null=False,blank=False,max_length=100,validators=[validators.EmailValidator])
addressCity = models.CharField(null=False,blank=False,max_length=100)
addressStreet = models.CharField(null=False,blank=False,max_length=200)
notes = models.TextField(null=True,blank=True)
accessKey = models.CharField(max_length=32,default=CreateAccessKey())
class PurchaseItem(models.Model):
picture = models.ForeignKey(Picture, null=False)
paperType = models.CharField(null=False,max_length=200)
printSize = models.CharField(null=False,max_length=200)
quantity = models.IntegerField(default=1, validators=[validators.MinValueValidator(1)])
price = models.DecimalField(decimal_places=2,max_digits=8)
dateCreated = models.DateTimeField(null=False)
cost = models.DecimalField(decimal_places=2,max_digits=8)
class OrderStatusHistory(models.Model):
orderId = models.ForeignKey(Order)
dateSet = models.DateTimeField(null=False,default=datetime.now())
status = models.IntegerField(choices=OrderStatus,default=0,null=False,blank=False)
comment = models.TextField(null=True,blank=True)
The following inline setup doesn't work because Order doesn't have a FK to PurchaseItems:
class OrderStatusHistoryAdmin(admin.StackedInline):
model = OrderStatusHistory
class PurchaseItemAdmin(admin.StackedInline):
model = PurchaseItem
class OrderAdmin(admin.ModelAdmin):
model = Order
inlines = [OrderStatusHistoryAdmin,PurchaseItemAdmin]
admin.site.register(Order,OrderAdmin)
Part 1
Use Inlines, that's very straight forward and django excels at this.
Part 2
Sure you could override your save for example and check if the drop down item has changed. If it has, generate your order status history object.
def save(self, *args, **kwargs):
if self._initial_data['status'] != self.__dict__['status']:
self.orderstatushistory_set.create("Status Changed!")
super(Order, self).save(*args, **kwargs)
You could do the same thing in the ModelAdmin too
def save_model(self, request, obj, form, change):
if obj._initial_data['status'] != obj.__dict__['status']:
# create whatever objects you wish!
Part 1:
You can 'nest' models with TabularInline or StackedInline admin models.
class OrderAdmin(admin.ModelAdmin):
model = Order
inlines = [
OrderStatusAdmin,
PurchaseItemAdmin
]
class OrderStatusAdmin(admin.StackedInline):
model = OrderStatus
class PurchaseAdmin(admin.StackedInline):
model = PurchaseItem
More information can be found here: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#inlinemodeladmin-objects
Part 2:
I need to be able to change the status of the order - selecting from a drop down or something - however, this action show be triggering a new statusHistory item creation.
For this you can use signals. There is a post_save and pre_save. So each time you save an order you can add extra logic. The pre_save signal has a sender and an instance so I think you can compare the status of the sender and the instance to be saved and if it changed you can add an other OrderStatus model.
More info can be found here:
http://docs.djangoproject.com/en/dev/ref/signals/#pre-save