Django: Iterating over a reversed many-to-many relationship - python

I have created a many-to-many relationship between my User model and my Projects model, and I want to list all the projects a specific user is part of on the user's profile page.
My models are:
class User(auth.models.User,auth.models.PermissionsMixin):
def __str__(self):
return "#{}".format(self.username)
class Prosjekter(models.Model):
id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
...
users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, verbose_name="Project participants")
...
The Django documentation states that:
Reverse m2m queries are supported (i.e., starting at the table that doesn’t have a ManyToManyField):
>>> Publication.objects.filter(article__id=1)
<QuerySet [<Publication: The Python Journal>]>
I am able to print the QuerySet of a specific user by typing (here illustrated with the user whose primary key is 2):
class UserDetailView(generic.DetailView):
model = User
template_name = 'accounts/user_detail.html'
results = Prosjekter.objects.filter(users__pk=2),
print(results)
This will return:
(<QuerySet [<Prosjekter: Project 1>, <Prosjekter: Project two>, <Prosjekter: Project third>]>,)
However, as you can see, I am only calling the user whose primary key is 2. Instead, I want to create a view that can be used in the general sense. Specifically, that I can see:
all user 1's projects on http://.../users/1/
all user 2's projects on http://.../users/2/
and so on
I am new to Python and Django and have trouble creating this view. Among many other attempts, I tried following this suggestion without success. Therefore, can you please help me with the following?
Can you update my UserDetailView code so that it works for all users?
Can you show me the code that I must implement on the /user_detail.html page (if I have understood it correctly, it should be a for loop that iterates through all the projects in the Prosjekter model and lists only those in which the specific user is a part of).

You are using DetailView wrong:
class UserDetailView(generic.DetailView):
model = User
template_name = 'accounts/user_detail.html'
def get_context_data(self, **kwargs):
ctx = super(UserDetailView, self).get_context_data(**kwargs)
ctx['results'] = Prosjekter.objects.filter(users=self.get_object())
return ctx
If you want more context variables, add them this way:
def get_context_data(self, **kwargs):
ctx = super(UserDetailView, self).get_context_data(**kwargs)
ctx.update({
'results': Prosjekter.objects.filter(users=self.get_object()),
'foo': 'bar',
'spam': 'ham',
})
return ctx

Related

How can you add to a ManyToManyField extension of the default Django User model?

For my app, I want to add one extra ManyToManyField to the default User model (django.contrib.auth.models.User). This extra field is called 'favorites' and the posts favorited by a user should go there. This is what I have:
class Favorite(models.Model):
user = models.OneToOneField(User, related_name='favorites', on_delete=models.CASCADE)
favorites = models.ManyToManyField(Recipe, related_name='favorited_by')
This is what I get when trying to add to 'favorites' from the shell.
# imported Recipe, Favorite, User(default)
>>> recipe1 = Recipe.objects.all()[0]
>>> me = User.objects.all()[0]
>>> me.favorites.add(recipe1)
django.contrib.auth.models.User.favorites.RelatedObjectDoesNotExist: User has no favorites.
# Just checking if the the User object, me, has a 'favorites' attribute
>>> 'favorites' in dir(me)
True
What is the correct way to add a Recipe object to this 'favorites' field?
For more reference, I did something similar how I handled Friendships between users, but it was a bit simpler since I wasn't extending the User model. The code for that is below and works fine:
class Friend(models.Model):
users = models.ManyToManyField(User)
current_user = models.ForeignKey(User, related_name='owner', null=True, on_delete=models.CASCADE)
#classmethod
def make_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.add(new_friend)
#classmethod
def lose_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.remove(new_friend)
Resolved. My solution is below, but I'm not sure if this is good practice.
django.contrib.auth.models.User.favorites.RelatedObjectDoesNotExist: User has no favorites.
The User model may have the 'favorites' field, but I needed to actually fill it with a 'Favorite' object. I did this by writing a function in my views.py:
def add_favorite(request, pk):
# Check if the user has a favorites field. If not create one and add. If yes, just add
user_favorites, created = Favorite.objects.get_or_create(
user=request.user
)
recipe = get_object_or_404(Recipe, pk=pk)
user_favorites.favorites.add(recipe)
This seems to work and I can access a user's favorites now, but I maybe this isn't good practice. With my method, new models that are created do not have a 'Favorite' object within it. That will only get created when a user decides to add a favorite recipe and the above view will create one if it doesn't already exist.

django-filter with DRF does not JOIN filter fields

Hi I'm having a weird issue with using 'django-filter' alongside the Django Rest Framework to filter an API.
If I use the following queryset it works as expected:
queryset = Project.objects.filter(assignments__user=self.request.user,assignments__role__name="Manager")
My Projects model has a ManyToMany relationship with Assignments. Each assignment has a user field (FK to User model) and a role field (FK to a ProjectRole model). When I run this queryset I get back all Projects where the current user is assigned AND the role is Manager.
When I try to acheive the same with the django-filters plugin however, it seems like I get back an OR joined expression (even though this should not be the default behaviour):
class ProjectFilter(django_filters.FilterSet):
class Meta:
model = Project
fields = ('assignments__role__name','assignments__user')
class ProjectViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
queryset = Project.objects.all()
return queryset
serializer_class = ProjectSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = ProjectFilter
I setup two filter fields for user and role, I get back every Project where within its assignments there is at least one assignment where the user is assigned (with any role) AND there is at least one assignment with a Manager role. However it does not check that this is 1 assignment, so I get back projects where current_user is a 'ReadOnly' role and user2 is 'Manager' role.

DjangoAdmin has_delete_permission executed from another Admin Class

I have two admin models, one is called Animal and another one is called Person. Each one has its own has_delete_permission on the ModelAdmin class.
The code I am using is listed below.
class Animal(models.Model):
sound = models.CharField(max_length=25, blank=True, null=True)
class Person(models.Model):
sound = models.CharField(max_length=25, blank=True, null=True)
class AnimalAdmin(admin.ModelAdmin):
model = Animal
def has_delete_permission(self, request, obj=None):
if request.POST and request.POST.get('action') == 'delete_selected':
animals = Animal.objects.filter( id__in = request.POST.getlist('_selected_action') )
print (animals)
return True
class PersonAdmin(admin.ModelAdmin):
model = Person
def has_delete_permission(self, request, obj=None):
return True
admin.site.register(Animal, AnimalAdmin)
admin.site.register(Person, PersonAdmin)
When I try to delete a Person instance that have the same ID of some Animals, the instances of Animals are printed. This could be a serious problem if I was doing some logic like changing the database or showing a message to the user.
The point is why has_delete_permission methods of different classes are also executed ?
This happens because of one method of the class AdminSite : each_context.
def each_context(self, request):
"""
Return a dictionary of variables to put in the template context for
*every* page in the admin site.
For sites running on a subpath, use the SCRIPT_NAME value if site_url
hasn't been customized.
"""
This method is called in every pages rendered in the admin backend, and it calls successively get_app_list(request) then _build_app_dict(request), which is looping over all admin models to check get_model_perms(request) :
def get_model_perms(self, request):
"""
Return a dict of all perms for this model. This dict has the keys
``add``, ``change``, ``delete``, and ``view`` mapping to the True/False
for each of those actions.
"""
return {
'add': self.has_add_permission(request),
'change': self.has_change_permission(request),
'delete': self.has_delete_permission(request),
'view': self.has_view_permission(request),
}
So if you override has_delete_permission in others ModelAdmin, it will be called too, each time you display a page.
You are seeing the instances of Animals matching the same indexes of the deleted Person because you filter the queryset with a simple list of indexes coming from request (request.POST.getlist('_selected_action')).
Note : nothing is printed when you are not in a POST request.
That said, you should avoid mixing different things in one function : has_delete_permission is about checking a delete permission on a model. I'm not sure what you want to check/prevent here, but you may find a better place.

Can't disable ForeignKey referential integrity check in Django 1.9

I have a model with two entities, Person and Code. Person is referenced by Code twice, a Person can be either the user of the code or the approver.
What I want to achieve is the following:
if the user provides an existing Person.cusman, no further action is needed.
if the user provides an unknown Person.cusman, a helper code looks up other attributes of the Person (from an external database), and creates a new Person entity.
I have implemented a function triggered by pre_save signal, which creates the missing Person on the fly. It works fine as long as I use python manage.py shell to create a Code with nonexistent Person.
However, when I try to add a new Code using the admin form or a CreateView descendant I always get the following validation error on the HTML form:
Select a valid choice. That choice is not one of the available choices.
Obviously there's a validation happening between clicking on the Save button and the Code.save() method, but I can't figure out which is it. Can you help me which method should I override to accept invalid foreign keys until pre_save creates the referenced entity?
models.py
class Person(models.Model):
cusman = models.CharField(
max_length=10,
primary_key=True)
name = models.CharField(max_length=30)
email = models.EmailField()
def __unicode__(self):
return u'{0} ({1})'.format(self.name, self.cusman)
class Code(models.Model):
user = models.ForeignKey(
Person,
on_delete=models.PROTECT,
db_constraint=False)
approver = models.ForeignKey(
Person,
on_delete=models.PROTECT,
related_name='approves',
db_constraint=False)
signals.py
#receiver(pre_save, sender=Code)
def create_referenced_person(sender, instance, **kwargs):
def create_person_if_doesnt_exist(cusman):
try:
Person = Person.objects.get(pk=cusman)
except Person.DoesNotExist:
Person = Person()
cr = CusmanResolver()
Person_details = cr.get_person_details(cusman)
Person.cusman = Person_details['cusman']
Person.name = Person_details['name']
Person.email = Person_details['email']
Person.save()
create_Person_if_doesnt_exist(instance.user_id)
create_Person_if_doesnt_exist(instance.approver_id)
views.py
class CodeAddForm(ModelForm):
class Meta:
model = Code
fields = [
'user',
'approver',
]
widgets = {
'user': TextInput,
'approver': TextInput
}
class CodeAddView(generic.CreateView):
template_name = 'teladm/code_add.html'
form_class = CodeAddForm
You misunderstood one thing: You shouldn't use TextField to populate ForeignKey, because django foreign keys are populated using dropdown/radio button to refer to the id of the object in another model. The error you got means you provided wrong information that doesn't match any id in another model(Person in your case).
What you can do is: not using ModelForm but Form. You might have some extra work to do after you call form.is_valid(), but at least you could code up your logic however you want.

Save a many-to-many model in Django/REST?

I'm writing a REST API for my Django app, and can't get POST requests to work on one model.
Here's the model in question:
class ProjectNode(models.Model):
name = models.CharField(max_length=60)
place = models.CharField(max_length=150)
time_spent = models.BigIntegerField()
parent_project = models.ForeignKey(Project, related_name='tasks')
workers = models.ManyToManyField(User, related_name='tasks_can_do')
def __str__(self):
return self.name
The User model just holds a name field at the moment.
Here's my serializer for ProjectNode:
class ProjectNodeSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectNode
fields = ('id', 'name', 'place', 'time_spent', 'workers',)
And here's the API view (from views.py):
class WebProjectNodeListView(generics.ListCreateAPIView):
queryset = ProjectNode.objects.all()
serializer_class = ProjectNodeSerializer
def pre_save(self, obj):
obj.parent_project = Project.objects.get(pk=self.request.DATA['parent_project'])
for worker_pk in self.request.DATA['workers']:
obj.workers.add(User.objects.get(pk=worker_pk))
obj.final_worker = User.objects.get(pk=self.request.DATA['final_workers'])
I tried a simpler version yesterday at first, which only had the Project ForeignKey relationship, and it seemed to work, so I thought that using add would work too, but I get an error when testing out the API with httpie (I already added some users and projects, and am sure I get their id's correctly).
Here's the request:
http POST :8000/api/tasks/ name="newtask" place="home" time_spent:=50 parent_project:=1 workers:=[1]
And I get this error:
"<ProjectNode: newtask>" needs to have a value for field "projectnode" before this many-to-many relationship can be used.
And the traceback also points to this line of code:
obj.workers.add(User.objects.get(id=worker_pk))
Now, I get the feeling that this is because I'm trying to update the relationship on the User object before a ProjectNode object is created in the database, but I'm not sure how to resolve this?
DRF doesn't works create models which are nested serializers objects or Many to Many fields.
So is necessary to override Serializer create method and create/get M2M models before create ProjectNode.
Try to override create(self, validated_data) in your serializer and work with your data inside this method..
Example:
My model Project has M2M relation with ProjectImages. In ProjectSerializer I override create method like this.
def create(self, validated_data):
try:
# Remove nested and M2m relationships from validated_data
images = validated_data.pop('projectimage_set') if 'projectimage_set' in validated_data else []
# Create project model
instance = Project(**validated_data)
if status:
instance.set_status(status)
project = instance.save()
# Create relations
for image in images:
ProjectImage.objects.create(project=project, **image)
except exceptions.ValidationError as e:
errors_messages = e.error_dict if hasattr(e, 'error_dict') else e.error_list
raise serializers.ValidationError(errors_messages)
return project
Hope this help!

Categories

Resources