Django get related info from Many to Many through table - django

I have something similar to the following model
class User(Model):
username = CharField(...)
class Event(Model):
event_name = CharField(...)
event_rating = IntegerField
users = ManyToManyField(User, through="UserEventAttendence")
class UserEventAttendence(Model):
user = ForeignKey(User)
event = ForeignKey(Event)
attended = BooleanField()
class EventComments(Model):
name = CharField(...)
content = CharField(...)
event = models.ForeignKey(Event, related_name='eventcomments', on_delete=models.CASCADE)
In my view I am trying to get all events, the users for each event, then from this filter out events that the current user is going to. Below is my current query (goes to a serializer afterwards and is then sent as a response)
events = models.Event.objects.using(db_to_use).prefetch_related(
Prefetch('users', queryset=models.UserProfile.objects.using(db_to_use))
).prefetch_related(
Prefetch('eventcomments', queryset=models.EventComments.objects.using(db_to_use))
).filter(usereventattendence__user_id=request.user.id)
But I need to somehow append the "attended" field from the "UserEventAttendence" model for each event (for the current user), but I don't have any clue how to go about it.

You can save the value of attended as an annotation on Event objects:
# events is your queryset as defined in the question
# I'm not entirely sure if the field name should be
# 'usereventattendence__attended'
# or
# 'user_event_attendence__attended'
events.annotate(attended=F('usereventattendence__attended'))
You can then access attended on the individual events:
for e in events:
print(e.attended)
One note on your intermediate model. It doesn't define unique constraints, which means that theoretically at least you could have several records for the same event-user pair (leading to duplicate Event objects in your query). If this is not desired, you would want to define a unique_together constraint on the intermediate model.

I think you have to get the attendence independently and build a list of tuples:
events_list = []
for event in events:
attended = event.usereventattendence_set.filter(user=request.user).attended
event_list.append((event, attended))
Then you can iterate:
for event, attended in event_list:
# do stuff
Or pass as context to the template processor.

Related

Minimizing Flagging System DB Cost

I am trying to figure out a way to make a flagging system that does not require a new instance to be entered in the database (Postgres) every time a user flags a video. (extra context below/above the code) The fields I would like to have are 'Description', 'Timestamp' and 'Flag Choice'.
So I was wondering if this would work. If I make a Flag model and make 'Flag Choices' (Gore, Excessive Violence, ect.) their own Positive Integer Fields and increment the fields accordingly and then combine the id of the post, the description for why they flagged the post, and the timestamp into ONE FIELD by separating new entries by commas into a TextField (In the User Model instead of the Flag model so I know who flagged whatever post)...Will that one Text Field eventually become too big? IMPORTANT: Every time a flag is reviewed and closed, it is deleted from said field (context below)
Context: In the Flag model there will be a post_id field along with Excessive Violence Gore ect. that are Positive Integer fields which are incremented every time someone submits a flag. Then in the User model there will be ONE field which will contain something like the following.
(Commas represent the split of the fields of 'post_id', 'description' and 'timestamp' in the database)
5, "Another flag from the same user in the same TextField.", 2019:9:15
# New Entry
...
Then to get the flag from that one field, I would use a regular expression in combination with a view (that passes a specific video as an argument from a flag management page) to get the post_id, description, timestamp from the TextField (recording the positions for slicing) then after the flag status is "Closed", the function will delete that slice (Starting with the post_id, ending with the timezone, slicing at the commas)
Will this work? The end result SHOULD be... When a post gets flagged, a new Flag model is made, at the same time (if this is the first flag from the user/the first flag for the post)a 'flag_info' field is created in the user model and the post_id, description, and timestamp are entered into said field. If that same user flags another video, a new instance is created for that specific post in the flag model and the flag choice (Gore, Excessive Violence, ect.) is incremented. At the same time the post_id, description, and timestamp are appended to the same field as the following "post_id; description; timestamp," and to grab a specific flag, use a regular expression ( and further processing on the moderation page ) to parse the post_id (used to view the specific post [which will be returned in a different function]) description, and timestamp.
Forgive me if this is difficult to understand, I'm still trying to figure this idea out myself.
I haven't found anything about this through google nor any other search engine.
Flag model
class Flag(models.Model):
FLAG_CHOICES =(
('Sexually Explicit Content', 'Sexually Explicit Content'),
('Child Abuse', 'Child Abuse'), # High priority, auto send to admin, ban if fake flag
('Promotes Definition Terrorism', 'Promotes Definition Terrorism'), # High priority, auto send to admin ban if fake flag
('Gore, Self Harm, Extreme Violence', 'Gore, Self Harm, Extreme Violence'),
('Spam/Misleading/Click-Bait', 'Spam/Misleading/Click-Bait'),
('Calling For Mass Flag', 'Calling For Mass Flag'),
('Doxing', 'Doxing'),
('Animal Abuse', 'Animal Abuse'),
('Threatening Behaviour', 'Threatening Behavior'),
('Calls To Action', 'Calls To Action')
)
STATUS_OPTIONS = (
('Open', 'Open'),
('Being Reviewed', 'Being Reviewed'),
('Pending', 'Pending'),
('Closed', 'Closed'),
)
objects = models.Manager()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
# Make positive integer fields for flag_choices so we can increment count instead of making a new instance every time
sexually_explicit_content = models.PositiveIntegerField(null=True)
child_abuse = models.PositiveIntegerField(null=True)
promotes_terrorism = models.PositiveIntegerField(null=True)
gore_harm_violence = models.PositiveIntegerField(null=True)
spam_clickbait = models.PositiveIntegerField(null=True)
mass_flag = models.PositiveIntegerField(null=True)
doxing = models.PositiveIntegerField(null=True)
animal_abuse = models.PositiveIntegerField(null=True)
threating_behaviour = models.PositiveIntegerField(null=True)
calls_to_action = models.PositiveIntegerField(null=True)
sexualizing_children = models.PositiveIntegerField(null=True)
# Increment the above fields when a flag with corresponding offence is submitted
who_flagged = models.TextField(null=True) # This will allow me to get all users who flagged a specific post (split by commas, and use a function to loop through the newly formed list of user ids, then on each iteration. I would be able to grab the user model for futher operations
flagged_date = models.DateTimeField(auto_now_add=True, null=True)
flag_choices = models.CharField(choices=FLAG_CHOICES, max_length=100, null=True) # Required Choices of offences
status = models.CharField(choices=STATUS_OPTIONS, default='Open', max_length=50, null=True)
def get_rendered_html(self):
template_name = 'vids/templates/vids/moderation.html'
return render_to_string(template_name, {'object': self.content_object})
User Model or Custom User Profile model
class CustomUser(models.Model):
...
reported = models.TextField() # This will hold all the information about the users flag
# Meaning the following things will be in the same 'box' (
flag_info) in the DB... and will look like this...
" post_id = 4; description = 'There was something in the background against the rules.'; timestamp = 2019:9:25,"
Then when the same user flags another video, something like the following would be appended to the 'flag_info' field...
All of this will be one big long string.
post_id = 24; description = "There was something in the background that showed my email."; timestamp = 2019:10:25,'
# To get flag_info from a user, I would do the following in a view
def get_flag(user, post_id):
# User is going to be the the user model that we need to pull from
# post_id is so I can use regex to pull the slice
# This is really simplified since it would take a while to write the whole thing
info = user.flag_info
split = info.split(",")
for i in split:
if i[0] == post_id:
# do something with it
# Alternatively I could do this
for i in split:
new = i.split(';')
# position 0 is the post_id, position 1 is description and position 3 is timestamp...Here I would do further processsing
To keep track of who flagged what I would make a TextField in the Flag model then every time a user flags a post, their user_id gets recorded in said TextField. When we need to review the flags, I would use the 'get_flag' function after splitting 'who_flagged' by commas. Which would extract the fields I need for processing.
Since I don't have thousands of videos/users, I can't test if the field will eventually become too large.

Django Model field : Ordered List of Foreign Keys

I have a Route model which should store an ordered list of stops along that route. How should I go about modeling this relation?
class Stop(models.Model):
name = ..
latitude = ..
longitude = ..
class Route(models.Model):
stops_list = # Ordered list of stops on the route
Since there are many Stops along a Route, and stops could belong to multiple routes, I would use a ManyToMany to store this relationship. You may specify a through model to store data about the relationship, such as what time the route is expected to arrive at this stop. There are many options to add order information. One naive way would be to have an Integer order field as below, or you could store order implicity via arrival_time. If these routes do not change often, IntegerField is not a terrible implementation. However, if they do change often then you would need to update the fields.... not ideal.
class Stop(models.Model):
name = ..
latitude = ..
longitude = ..
class Route(models.Model):
stops_list = models.ManytoManyField(Stop, through='StopInfo') # Ordered list of stops on the route
class StopInfo(models.Model):
""" Model for storing data about the Stop/Route relationship """
stop = models.ForeignKey(Stop)
route = models.ForeignKey(Route)
arrival_time = models.DateTimeField(auto_now_add=True)
order = models.PositiveIntegerField()

Django - join two models

For the following models I want to retrieve all the devices that have an entry in the History table with transition_date between a specified interval:
class History(models.Model):
device = models.ForeignKey(DeviceModel, to_field='id')
transition_date = models.DateTimeField()
class Meta:
db_table = 'History'
class DeviceModel(models.Model):
id = models.IntegerField()
name = models.CharField()
class Meta:
db_table = 'Devices'
I have this code that filters for the specified interval:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate))
That gives me as many rows as History table has with transition_date in the specified range.
The filter function performs an INNER JOIN between DeviceModel and History on device id retrieving only DeviceModel fields. My question is how do I retrieve data from both History and DeviceModel at the same time while joining them as with filter/select_related on device id.
I'd rather not write a custom SQL query.
In your models Device and History models are related with a foreign key from History to DeviceModel, this mean when you have a History object you can retrieve the Device model related to it, and viceversa (if you have a Device you can get its History).
Example:
first_history = History.objects.all()[0]
first_history.device # This return the device object related with first_history
first_history.device.name # This return the name of the device related with first_history
But it works also in the other way, you could do:
first_device = Device.objects.all()[0]
first_device.history # This return the history object related with device
first_device.history.transition_date # Exactly as before, can access history fields
So in your query:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate))
This return a device list, but you can access to the history related with each device object
Isn't that enough for you ? You have a Device list, and each device can access to its related History object
Info: When you declare a ForeignKey field the models are related by id for default, I say this because you're doing:
device = models.ForeignKey(DeviceModel, to_field='id')
as you can see you're using to_field='id' but this relation is done by default, if you do:
device = models.ForeignKey(DeviceModel)
You'll get same results
(EDIT) Using .values() to obtain list [device.name, history.date]
To get a list like you said [device.name, history.date] you can use .values() function of Django QuerySet, official documentation here
You can try something like:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate)).values('name','history__transition_date')
# Notice that it is 'history _ _ transition_date with 2 underscores

Why is get get_queryset() not returning the right results?

I have two models, Recieved_order and order,
class Order(SmartModel):
restaurant = models.ForeignKey(Restaurant,null=True,blank=True,default = None,help_text="The restaurant the customer order from")
#contact info
email = models.EmailField(max_length=50,help_text="Needed as alternative")
mobile = PhoneNumberField(max_length=20,default='+25078######')
class Recieved_Order(SmartModel):
item = models.ForeignKey(Item)
date_added = models.DateTimeField(auto_now=True,auto_now_add=True)
quantity = models.IntegerField(default=0)
price = models.DecimalField(max_digits=9,decimal_places=2)
order = models.ForeignKey(Order)
i want a restaurant manager(user), to be able to receive orders(Recieved_order) made to his specific restaurants when logged in, to achieve this, i have the following in views.py
class Recieved_OrderCRUDL(SmartCRUDL):
model = Recieved_Order
actions = ('create','read','update','delete','list')
permissions = True
class List(SmartListView):
fields = ('order_email','order_mobile','order_billing_city','item.name','item.price','quantity','order_id','order_restaurant')
search_fields = ('date_added',)
def get_queryset(self,*args,**kwargs):
queryset = super(Recieved_OrderCRUDL.List, self).get_queryset(*args,**kwargs)
if self.request.user.is_superuser:
return queryset
return queryset.filter(order=self.request.user)
with the above i am testing on two different restaurants, the restaurant and its not working out as it should. its returning the wrong orders for a given restaurant.
What am i not doing right with get_queryset().
There's something confusing going on here:
return queryset.filter(order=self.request.user)
You're telling it to build a query that filters Order objects against User objects.
Is there something missing in your sample code that ties orders back to users such that a proper join can be constructed?
If you want to have a user (what you refer to as a manager) only able to view their own orders, you need to change things... Restaurant will need to have a field that points to a User (let's call it user and assume it's a ForeignKey) Then you can do something like
if self.request.user.is_superuser:
return queryset
return queryset.filter(order__restaurant__user=self.request.user)
As pointed out by #Joe Holloway, you should not be trying to filter on the order field with a user object...
The other odd thing I wanted to point out is
fields = ('order_email','order_mobile','order_billing_city','item.name','item.price','quantity','order_id','order_restaurant')
You appear to be using a mixture of ways to attempt to access things...
You should be using __ (that's 2 underscores) to access relations, not _ or .

Storing the edit history of a django model in another custom model

I have two models lets say:
class superfields(Model):
fieldA = models.FloatField()
fieldB = models.FloatField()
class Meta:
abstract = True
class my_model( superfields ):
def has_history( self ):
return self.my_model_history_set.count() > 0
class my_model_history( superfields ):
reason = models.TextField()
mymodel = models.ForeignKey( my_model )
'my_model' is populated with data (under fieldA and fieldB). Whenever someone edits 'my_model's fields and saves, I don't want to save the change in this model but want to store it as a new row with all values in 'my_model_history', in addition to a 'reason' field while 'my_model' data stays the same.
What is the best way to approach this scenario in terms of custom templates, custom views, model admins etc etc. Am I doing it correctly?
To give my question above some sense, in my project, the nature of data under 'my_model' is market prices and I need to maintain a history of all the market prices ever edited with a 'reason' for the edit.
Instead of editing an existing entry, why not use that entry as initial data for a form to create a new instance? The new object gets saved, the original stays the same...
My Solution:
yes. A simple and quick solution I am following is as follows:
I create three models similar to this:
class my_super_abstract_model(Model):
#All fields I need to keep a history for:
fieldA = models.FloatField()
fieldB = models.FloatField()
class Meta:
abstract = True
class my_model( my_super_abstract_model ):
def has_history( self ):
return self.my_model_history_set.count() > 0
class my_model_history( my_super_abstract_model ):
reason = models.TextField()
history_entry_for = models.ForeignKey( my_model )
I've setup a signal:
pre_save.connect( create_history,
sender = my_model_history )
and 'create history' to be called by the pre_save() signal before saving in my_model_history:
def create_history(sender, **kwargs):
#get variables passed by the pre-save signal:
history_model = kwargs['instance']
# Get main model object
main_model = history_model.history_entry_for
# swap all common fields between history edit and main model (except id)
main_model_fields = [f.name for f in main_model._meta.fields]
history_model_fields = [f.name for f in history_model._meta.fields]
field_index = list( [f for f in history_model_fields if f in main_model_fields and f != 'id' and f != 'created_date' ] )
#loop thru to swap values:
for field_name in field_index:
temp = getattr(main_model, field_name)
setattr( main_model, field_name, getattr( history_model, field_name ) )
setattr( history_model, field_name, temp)
# After the swap, save main model object here
main_model.save()
Whenever user clicks on a my_model row for editing, I use 'my_model_history' to generate my edit form and populate it with the values from the user selected row. (Have written a view and template to do that)
So the edit form will now have:
field A -populated with values from
my_model data row
field B -populated with values from
my_model data row
Reason -empty text box
history_entry_for -hidden from view
User can now edit fieldA/fieldB. Enter a reason. Press save to trigger the signal above.
Before saving,
Signal will swap the values between
the main model(old values) and
history model(New values)
Replace and save the main model row
(with the new values).
Insert and save a new row in the
history model (with the old values)
with a reason.
Hope it helps. Let me know if there are any further questions.
I found an explanation on keeping detailed edit histories in the book 'pro Django' page 264. After a read through I'll try an implementation of what I need. Will post my approach here when I'm done