I have the following models:
class Application(models.Model):
users = models.ManyToManyField(User, through='Permission')
folder = models.ForeignKey(Folder)
class Folder(models.Model):
company = models.ManyToManyField(Compnay)
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
company = models.ManyToManyField(Company)
What I would like to do is to check whether one of the users of the Application has the same company as the Application (via Folder). If this is the case the Application instance should not be saved.
The problem is that the ManyToManyFields aren't updated until after the 'post-save' signal.
The only option seems to be the new m2m_changed signal. But I'm not sure how I then roll back the save that has already happened.
Another option would be to rewrite the save function (in models.py, because I'm talking about the admin here), but I'm not sure how I could access the manytomanyfield content.
Finally I've read something about rewriting the save function in the admin of the model in admin.py, however I still wouldn't know how you would access the manytomanyfield content.
I have been searching for this everywhere but nothing I come across seems to work for me.
If anything is unclear, please tell me.
Thanks for your help!
Heleen
Because I didn't get a reply from Botondus I decided to ask a new question in the Django Users Google Group and finally got the answer from jaymz.
I figured that Botondus method was the right way of doing it, it just wasn't quite working. The reason that it doesn't work in this case is because I'm using a Through model for the field I would like to do the validation on. Because of some earlier feedback I got on a previously posted question I gathered that first the Application instance is saved and then the ManyToMany instances are saved (I believe this is right, but correct me if I'm wrong). So I thought that, if I would perform the validation on the ManyToMany Field in the Through model, this would not prevent the Application instance being saved. But in fact it does prevent that from happening.
So if you have a ManyToMany Field inline in your model's admin and you would like to do validation on that field, you specify the clean function in the through model, like this:
admin.py
class PermissionInline(admin.TabularInline):
form = PermissionForm
model = Permission
extra = 3
forms.py
class PermissionForm(forms.ModelForm):
class Meta:
model = Permission
def clean(self):
cleaned_data = self.cleaned_data
user = cleaned_data['user']
role = cleaned_data['role']
if role.id != 1:
folder = cleaned_data['application'].folder
if len(filter(lambda x:x in user.profile.company.all(),folder.company.all())) > 0: # this is an intersection
raise forms.ValidationError("One of the users of this Application works for one of the Repository's organisations!")
return cleaned_data
If the validation results in an error NOTHING (neither the application instance, nor the manytomany users instances) is saved and you get the chance to correct the error.
forms.py
class ApplicationForm(ModelForm):
class Meta:
model = Application
def clean(self):
cleaned_data = self.cleaned_data
users = cleaned_data['users']
folder = cleaned_data['folder']
if users.filter(profile__company__in=folder.company.all()).count() > 0:
raise forms.ValidationError('One of the users of this Application works in one of the Folder companies!')
return cleaned_data
admin.py
class ApplicationAdmin(ModelAdmin):
form = ApplicationForm
Edit: Replaced initial (wrong) model validation example with form validation.
Related
I have two models named 'Message' and 'Ticket'. Message has a Foreignkey to Ticket. I showed Messages of a Ticket in django admin, using StackedInline. But the problem is I want that the already created messages be read-only, while being able to create new message, too.
I've also check bunch of questions; like this or this. But none of the was helpful! Or at least, I could not get the clue!
This is my code:
models.py:
class Ticket(models.Model):
title = models.CharField(max_length=128)
#...
class Message(models.Model):
text = models.TextField()
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
attachment = models.FileField(upload_to=some_url_pattern)
sender = models.CharField(max_length=2, editable=False)
admin.py:
class MessageInline(admin.StackedInline):
model = Message
extra = 1
def get_readonly_fields(self, request, obj=None):
if obj:
return ['text', 'attachment']
else:
return []
#admin.register(Ticket)
class ResponderAdmin(admin.ModelAdmin):
fields = ['title']
inlines = [MessageInline]
As can be seen, I tried to achieve the goal by overriding get_readonly_fields but this is what happend:
the screenshot of admin page
As can be seen in the picture, every message inlines has been made read-only and I can not add a new message...
Can anyone help me with this issue?
I am assuming this is for admin.
Remove the user's SuperUser access but leave them with Staff access. Then use permissions to give them Add and access for the specific model but do not give them Update or Delete access. That should enable them to view the data without being able to change or delete it.
I have my user table in django, and to differ all the users I created two tables, (Teacher and Student).
Both tables are getting an fk from user
So, in order to make authorization how do I check if one's user is in a certain table.
I need to check it this way
def test_func(self):
return self.request.user.check..if..it..exists..in..table
My models are like this.
class Teacher(models.Model):
User = models.OneToOneField(settings.AUTH_USER_MODEL)
This depends on how your models are set up.
If your Teacher model looks something like this;
class Teacher(models.Model):
user = models.ForeignKey(User)
Then you should be able to check if the user is a teacher by using the implicit backref;
self.request.user.teacher_set.exists()
As the question has been updated to show that the model is slightly different than I anticipated, here is an update.
class Teacher(models.Model):
user = models.OneToOneField(User)
Which means that the backref will be a little different.
hasattr(self.request.user, "teacher")
As you've mentioned that you are doing this inside a django template, I'm pretty sure that the following will work:
{% if user.teacher %}
Since you haven't posted your models, I am giving you a rough idea how to do it.
in your views.py -
from .models import Teacher,Student
def test_func(request):
user = request.user
if (Teacher.objects.filter(user=user).count() > 0) or (Student.objects.filter(user=user).count > 0):
#do your stuffs here..
One way is to query both tables:
teacher = Teacher.objects.filter(user=self.request.user)
student = Student.objects.filter(user=self.request.user)
if teacher or student:
# do what you want.
If you put in your relation the argument "related_name" you can do it using inverse relationship
class SomeTable(models.Model):
user = models.ForeignKey(
User, #Your user model or Django one
verbose_name = "User",
related_name = "inverse_relation_name"
)
Then you have to call using keyword arguments for the filters:
SomeTable.inverse_relation_name.filter(id=self.request.user.id) #You will get a queryset
Or
SomeTable.inverse_relation_name.get(id=self.request.user.id) # You will get the object or a exception
class A(Model):
to_b = ManyToManyField('B', blank=True, through='AtoB')
class B(Model):
to_a = ManyToManyField('A', blank=True, through='AtoB')
class AtoB(Model):
a = ForeignKey('A', on_delete=CASCADE)
b = ForeignKey('B', on_delete=CASCADE)
usr = ForeignKey(settings.USER, on_delete=CASCADE)
# some other fields
Im making a django application.
This is roughly equivalent to what i have in my models.py
I need m2m relation between A and B to go through another model because i need to store additional data there.
Now there is a problem - when i try to save instance of model A in my custom view, relations with B instances are not saved no matter whether i pick them or not.
And when i go to http://127.0.0.1:8000/admin and try to create instance of A from there, i dont even see proper field (should be <select multiple> i guess) for selecting relations with B.
Can someone please explain me why relations are not saved, and not even displayed in /admin?
Here is code roughly equivalent to what i have in views.py:
class Create(CreateView):
model = None # A or B
template_name = 'something.html'
def form_valid(self, form):
self.object = form.save(commit=False)
form.save()
return HttpResponseRedirect(self.get_success_url())
in urls.py i specify additional arguments like this:
views.Create.as_view(model=models.A, fields=['to_b'])
It is not going to work. If you are using your own ManytoMany intermediary table, you have to manually manage and save the objects yourself. Using Django's builtin functions won't work.
Save Object A, then save Object B and then save the relation in your AtoB table (which is also an object).
a_to_b = AtoB.objects.create(a=object_a, b=object_b, user=self.request.user)
print(a_to_b)
[...] Note that if you are using an intermediate
model for a many-to-many relationship, some of the related manager’s
methods are disabled, so some of these examples won’t work with such
models.
https://docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many/
Your error is explained here: https://docs.djangoproject.com/en/1.10/topics/db/models/#intermediary-manytomany
I have come to an impasse when using a ModelForm.
I'm extending the User model that comes with Django, and I'm also using a ModelForm so the user can edit it.
Following the same example in the documentation, I would have this code.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# In this case, department is optional, so I have set 'blank' and 'null' to True.
department = models.CharField(max_length=100, blank=True, null=True)
forms.py
class DepartmentForm(ModelForm):
class Meta:
model = Employee
fields = ['department',]
The problem comes at the view. I found that I need to pass an instance of the model to the form so the save() function works without having to customize it, but of course, user.employee has not been created yet, therefore it throws an error.
views.py
def DepartmentView(request):
# Here is the issue.
department = request.user.employee
if request.method == 'POST':
# I need to pass the instance here.
form = DepartmentForm(request.POST, instance=department)
if form.is_valid():
form.save()
else:
# And also here so it autocompletes the form.
form = DepartmentForm(instance=department)
return render(request, 'employee.html', {'form': form})
It works if I manually add a value to user.employee.department through the shell and then reload the page, otherwise the error is as follow.
RelatedObjectDoesNotExist at [something]
User has no employee.
Or something like that... I'm sorry, I didn't try the code above so the error could be a little different, but the concept is exactly the same.
I'm also sorry if this has been asked before. I did a Google search and couldn't find an answer to this issue.
You could use get_or_create to fetch the employee from the db, or create it if it doesn't exist.
department, created = Employee.objects.get_or_create(user=request_or_user, department='')
if request.method == 'POST':
form = DepartmentForm(request.POST, instance=department)
...
Another option is to use a signal, so that the related model is created when the user is created. Then you can assume that the employee already exists, and you can use request.user.employee instead of get_or_create.
Despite having read several things on this topic, i'm seeing myself stuck.
I have a Django Rest Framework Application. I have this model :
class MyModel(Model):
name = CharField(_('name'), max_length=50)
type = CharField(_('type'), max_length=50)
users_selected = ManyToManyField(User)
Which i'm trying to serialize. I don't want to expose the users_selected as is seeing as it has all the users of the application whom selected this specific model, but just be able to put a boolean saying if it's selected for the current user, which i achieved by doing :
class MySerializer(ModelSerializer):
is_selected = SerializerMethodField('user_select')
def user_select(self, obj):
request = self.context['request']
return obj.users_selected.filter(id=request.user.id).exists()
class Meta(object):
model = MyModel
exclude = ('users_selected',)
This is working fine, but now i want to do it the other way around, meaning i want my client to be able to send a request with a is_selected set to true or false and then modify my users_selected accordingly. The SerializerMethodField implies the fact that this is a read-only field.
How should I achieve this? I've tried to search by using the restore_object method, but was unable to achieve anything...
Thanks.
To me, it feels a little strange to have MyModel point to all of the Users who selected it. This would be simpler if you subclassed the User, and gave the custom User either a Foreign key or a ManyToManyField to the MyModels selected.
If you did it this way, a single User could access his own selections, and there would be no permissions problems. The only time someone would need to access Users directly from the MyModel object would be for administrative work, at which time it would be perfectly fine to expose all of the users in the list. You could even have a separate view, something like MyModelUsersSelected, which could look like:
class MyModelUsersSelected(generics.ListAPIView):
serializer_class = UserSerializer
permission_classes = (permissions.IsAdminUser,)
def get_queryset(self):
return MyModel.objects.get(id=self.kwargs['pk']).user_set.all()
This would give you all of the Users who pointed to the MyModel instance passed as a pk in the url. Since this view is separate from the MyModel and User views, permissions can be set to admin users only.