ManyToMany on Self Model: How to add new instances to this relation - python

I have a Profile class, which extends the User class, and I would like to allow every profile to be able to bookmark other user profiles.
I've set up the following:
class Profile(models.Model):
# some fields e.g. name, email
bookmarked_profiles = models.ManyToManyField("self")
Now, I'm a bit confused, for example, how would I 'bookmark' new profiles for a given profile instance?
E.g. something like
# add new bookmark for profile instance
p = Profile.objects.get(pk=1)
profiles_to_bookmark = Profile.objects.all()
p.bookmarked_profiles = profiles_to_bookmark
I would also like to know the number of bookmarked profiles e.g.
p.bookmarked_profiles.count()

Check in django docs, there is everything that you need there
for profile in profiles_to_bookmark:
p.bookmarked_profiles.add(profile) # WIll add New objects
p.bookmarked_profiles.all() # Will return all objects
p.bookmarked_profiles.all().count() # Will return count of objects
https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_many/strong text

You can do so by adding the queryset via add() available in ManyToMnayField()
p = Profile.objects.get(pk=1)
profiles_to_bookmark = Profile.objects.all()
p.bookmarked_profiles.add(*profiles_to_bookmark) #Note the star *
And with all() you can set all bookmarked profiles, and count() will return the total
p.bookmarked_profiles.all()
p.bookmarked_profiles.count()

Related

Django REST API query on related field

I have 3 models, Run, RunParameter, RunValue:
class Run(models.Model):
start_time = models.DateTimeField(db_index=True)
end_time = models.DateTimeField()
class RunParameter(models.Model):
parameter = models.ForeignKey(Parameter, on_delete=models.CASCADE)
class RunValue(models.Model):
run = models.ForeignKey(Run, on_delete=models.CASCADE)
run_parameter = models.ForeignKey(RunParameter, on_delete=models.CASCADE)
value = models.FloatField(default=0)
class Meta:
unique_together=(('run','run_parameter'),)
A Run can have a RunValue, which is a float value with the value's name coming from RunParameter (which is basically a table containing names), for example:
A RunValue could be AverageTime, or MaximumTemperature
A Run could then have RunValue = RunParameter:AverageTime with value X.
Another Run instance could have RunValue = RunParameter:MaximumTemperature with value Y, etc.
I created an endpoint to query my API, but I only have the RunParameter ID (because of the way you can select which parameter you want to graph), not the RunValue ID directly. I basically show a list of all RunParameter and a list of all Run instances, because if I showed all instances of RunValue the list would be too long and confusing, as instead of seeing "Maximum Temperature" you would see:
"Maximum Temperature for Run X"
"Maximum Temperature for Run Y"
"Maximum Temperature for Run Z", etc. (repeat 50+ times).
My API view looks like this:
class RunValuesDetailAPIView(RetrieveAPIView):
queryset = RunValue.objects.all()
serializer_class = RunValuesDetailSerializer
permission_classes = [IsOwnerOrReadOnly]]
And the serializer for that looks like this:
class RunValuesDetailSerializer(ModelSerializer):
run = SerializerMethodField()
class Meta:
model = RunValue
fields = [
'id',
'run',
'run_parameter',
'value'
]
def get_run(self, obj):
return str(obj.run)
And the URL just in case it's relevant:
url(r'^run-values/(?P<pk>\d+)/$', RunValuesDetailAPIView.as_view(), name='values_list_detail'),
Since I'm new to REST API, so far I've only dealt with having the ID of the model API view I am querying directly, but never an ID of a related field. I'm not sure where to modify my queryset to pass it an ID to get the appropriate model instance from a related field.
At the point I make the API query, I have the Run instance ID and the RunParameter ID. I would need the queryset to be:
run_value = RunValue.objects.get(run=run_id, run_parameter_id=param_id)
While so far I've only ever had to do something like:
run_value = RunValue.objects.get(id=value_id) # I don't have this ID
If I understand correctly, you're trying to get an instance of RunValue with only the Run id and the RunParameter id, i.e. query based on related fields.
The queryset can be achieved with the following:
run_value = RunValue.objects.get(
run__id=run_id,
run_parameter__id=run_parameter_id
)
Providing that a RunValue instance only ever has 1 related Run and RunParameter, this will return the instance of RunValue you're after.
Let me know if that's not what you mean.
The double underscore allows you to access those related instance fields in your query.
Well its pretty simple, all you have to do is override the get_object method, for example(copy pasted from documentation):
# view
from django.shortcuts import get_object_or_404
class RunValuesDetailAPIView(RetrieveAPIView):
queryset = RunValue.objects.all()
serializer_class = RunValuesDetailSerializer
permission_classes = [IsOwnerOrReadOnly]]
lookup_fields = ["run_id", "run_parameter_id"]
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
# url
url(r'^run-values/(?P<run_id>\d+)/(?P<run_parameter_id>\d+)/$', RunValuesDetailAPIView.as_view(), name='values_list_detail'),
But one big thing you need to be careful, is not to have duplicate entries with same run_id and run_parameter_id, then it will throw errors. To avoid it, either use unique_together=['run', 'run_parameter'] or you can use queryset.filter(**filter).first() instead of get_object_or_404 in the view. But second option will produce wrong results when duplicate entries are created.

Django Manager for Group model does not work - returns empty queryset

I'm junior dev. I want to create managers for Django Groups. One new one and one that will override default manager
EDIT: Django 1.8, python 2.7.15
My managers:
class DefaultGroupManager(models.Manager):
def get_queryset(self):
test_ids = Test.objects.values_list('rel_group_id', flat=True)
return super(DefaultGroupManager, self).get_queryset().exclude(id__in=test_ids)
class AllGroupsManager(models.Manager):
def get_queryset(self):
return super(AllGroupsManager, self).get_queryset().exclude(rel_group__start_date__lte=datetime.now()-timedelta(days=30))
With these managers I created something like this:
dgm = DefaultGroupManager()
agm = AllGroupsManager()
agm.contribute_to_class(Group, 'get_all')
dgm.contribute_to_class(Group, 'objects')
And it was working. I could use Group.get_all.all() and new Group.objects.all(). In return I had proper lists of objects.
But my senior dev said that I have to do it by creating the new Group model that inherits from Group. So I did:
My Group model:
class GroupModel(Group):
get_all = DefaultGroupManager()
objects = AllGroupsManager()
But it does not work!
When I use GroupModel.get_all.all() or overrided GroupModel.objects.all() it returns empty list [] instead of list with loads of objects.
Everything seems to be good :(
I will appreciate any help!
If you're defining a new class, you definitely want to make it a proxy for Group. Otherwise it will have its own database table, which as you've found won't have any data in it.
class GroupModel(Group):
get_all = DefaultGroupManager()
objects = AllGroupsManager()
class Meta:
proxy = True

Updating Django Forms Dynamically

I have a simple form that provides a drop down menu of all available entries in a django Model.
forms.py
class SampleNameLookupForm(forms.Form):
#Make a list of sample name options for dropdown menu.
# Turn that into a list of tuples for ChoiceField.
samples = Sample.objects.all()
sample_list = [i.sample_name for i in samples]
sample_tuple = [(i, i) for i in sample_list]
Sample_Name = chosenforms.ChosenChoiceField(sample_tuple)
models.py
class Sample(models.Model):
sample_ID = models.CharField(max_length=20)
sample_name = models.CharField(max_length=30)
def __unicode__(self):
return self.sample_ID
class Meta:
ordering = ['id']
When I add a new sample to the model, I can see the new addition when accessing the model in mysql, or in the python manage.py shell, but it does not appear in the chosen field drop down menu.
When I restart the server, the form then reflects the new sample in the drop down menu. How can I update the drop down menu without restarting the server?
I should mention I am just locally hosting and am currently not in production.
The code needs to be inside the form's __init__ method, rather than directly in the class body, since the method is evaluated on each form instantation, rather than only when the class is defined.
class SampleNameLookupForm(forms.Form):
def __init__(self):
#Make a list of sample name options for dropdown menu.
# Turn that into a list of tuples for ChoiceField.
samples = Sample.objects.all().values_list('sample_name', flat=True)
sample_tuple = [(i, i) for i in samples]
self.fields['Sample_Name'] = chosenforms.ChosenChoiceField(sample_tuple)
Also note, I don't know what ChosenChoiceField is, but the standard Django forms library has a ModelChoiceField which is intended specifically for this use; it takes a queryset argument which is lazily evaluated, and therefore avoids the need for the __init__ stuff.

Catch model objects unchanged in admin interface

I have models Car and Seat, with Seat having a foreign key to Car.
In models.py:
class Car(models.Model):
# ...
class Seat(models.Model):
car = models.ForeignKey(Car)
# ...
In the admin interface, seats can be added to a car when adding/changing a car (using inline fields).
In admin.py:
class CarAdmin(admin.ModelAdmin):
# ...
inlines = [SeatInline]
class SeatInline(admin.StackedInline):
model = Seat
extra = 1
When a user adds/changes/deletes a car or adds/changes/delete seats via the CarAdmin, I need to log what he did and therefore needs to compare in particular all the seats before and after the change.
The question is:
How to get two lists of seat objects seats_before and seats_after in order to compare them? This needs to be done somewhere where I can access request.user.
I tried to do that in save_formset(), but it did not work. One of the problems: To get seats_after, it is apparently only possible to obtain the list of seats that have been modified, i.e. there is no way to make the difference between an unchanged seat and a deleted seat.
def save_formset(self, request, form, formset, change):
# ...
instances = formset.save(commit=False)
seats_after = []
for instance in instances:
if isinstance(instance, Seat):
seats_after.append(instance) # Unchanged seats are not added here
# ...
I also have trouble to get seats_before in this function.
EDIT:
Following defuz' suggestion, I tried this piece of code:
def save_related(self, request, form, formsets, change):
car_before = form.save(commit=False)
seats_before = car_before.seats_set.all()
form.save_m2m()
seats_after = []
for formset in formsets:
instances = formset.save()
for instance in instances:
if isinstance(instance, Seat):
seats_after.append(instance)
In this example there are two problems:
seats_before seems to contain the new seats instead of the old ones, like if the related objects were saved when the form was saved.
seats_after contains only changed/added seats. Unchanged and deleted seats do not appear (and I would like to have the unchanged seats included).
Use ModelAdmin.save_related: method.
Django signals might be what you're looking for.
https://docs.djangoproject.com/en/dev/ref/signals/

Way to allow for duplicate many-to-many entries in Python/Django

I have the following Django model:
class Icon(models.Model):
name = models.CharField(max_length=200,null=False,blank=False)
class Post(models.Model):
icons = models.ManyToManyField(Icon)
When I write the following code:
post = Post()
icons = []
icon_id = form.cleaned_data['icon_1']
if (icon_id):
i = Icon.objects.get(id=icon_id)
icons.append(i)
icon_id = form.cleaned_data['icon_2']
if (icon_id):
i = Icon.objects.get(id=icon_id)
icons.append(i)
post.icons = icons
post.save()
It works fine for the most part, creating a Post object and the two Icon objects.
However, if the icon_id is, say, 1 in both cases, it only creates ONE entry in the database, not two.
So it seems like it checks for duplicates and removes them.
How do I make this work so I allow duplicates? (I want two of the SAME icon associated with a post.)
Thanks!
Define the model yourself, to have such non-unique many-to-many relations
class PostIcon(models.Model):
post = models.ForeignKey(Post)
icon = models.ForeignKey(Icon)
and than add them one by one
for icon in icons:
PostIcon(post=post, icon=icon).save()
or pass that model as through argument of ManyToManyField e.g.
class Post(models.Model):
icons = models.ManyToManyField(Icon, through=PostIcon)
or alternatively you can have a count associated with PostIcon instead of having multiple rows, if that serves the use-case e.g. you may want a badge to be displayed 10 times
class PostIcon(models.Model):
post = models.ForeignKey(Post)
icon = models.ForeignKey(Icon)
count = models.IntegerField()

Categories