Simply asked: In a form, how do you restrict the display of a many-to-many relationship to the records the user has defined himself?
In an effort to keep things as DRY as possible, I'm using forms.ModelForm.
Here in Example, a user can create feed-items which run on a schedule. Technically the feeds and the items are connected to the user by user = models.ForeignKey(User) however that in itself is not enough to restrict the association of the two through the user himself.
While I'd love to inherently restrict association of the two THROUGH the user, I'd be happy to simply restrict the display of the items via user and simply validate afterwards. Example data below:
class Example(models.Model):
global CHOICES_DAYS
global CHOICES_HOURS
user = models.ForeignKey(User)
label = models.CharField(max_length=180)
title = models.CharField(max_length=1000, blank=True)
content = models.TextField(max_length=10000, blank=True)
Example_feeds = models.ManyToManyField(ExampleFeed, blank=True)
hourly_schedule = models.CharField(choices=CHOICES_HOURS, max_length=2, default=4)
weekly_schedule = models.CharField(choices=CHOICES_DAYS, max_length=5, default=1)
last_used = models.DateTimeField(default=timezone.now, blank=False)
def __str__(self):
return self.label
class Meta:
ordering = ('last_used',)
And here is the form, of which I'm using forms.ModelForm to keep things as DRY as possible:
class ExampleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_id = ''
self.helper.form_class = ''
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
super(ExampleForm, self).__init__(*args, **kwargs)
class Meta():
model = Example
fields = [
'label',
'title',
'content',
'example_feeds',
'hourly_schedule',
'weekly_schedule']
You need to pass the request.user to the form when you are instantiating it in your view and then make use of that request object to restrict the form field's queryset.
class ExampleForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
super(ExampleForm, self).__init__(*args, **kwargs)
self.fields['mym2mfield'].queryset = Example.objects.filter(example_feeds__user=request.user)
and in your view (assuming it's a class based view):
class MyView(FormView):
def get_form(self, form_class):
return form_class(self.request, self.get_form_kwargs())
Related
I've been trying to set up a basic private messaging system in Django using the generic CreateView.
I am currently having trouble with the "Receiver"/"To" field in my form. I tried to make it so it was a drop down field with the options being followers of the logged-in user.
Currently, the field is populating with the correct usernames (in this case, "testuser1") but it is throwing an error saying this field needs to be populated with an instance of the User object.
ValueError: Cannot assign "'testuser1'": "Message.reciever" must be a "User" instance.
Is there a way to have the form pass in the object of the username that is selected?
Model:
class Message(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sender")
reciever = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reciever")
subject = models.CharField(max_length=128, default="-")
content = models.TextField()
send_date = models.DateTimeField(default=timezone.now)
User Relationships Model:
class UserRelationships(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
created = models.DateTimeField(auto_now_add=True)
UPDATED Form:
class MessageCreateForm(forms.ModelForm):
class Meta:
model = Message
fields = ['sender', 'reciever', 'subject', 'content']
widgets = {'sender': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
follower_objects = kwargs.pop('follower_objects')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'] = RecieverModelChoiceField(queryset=User.objects.filter(username__in=follower_objects))
View:
class MessageCreateView(LoginRequiredMixin, CreateView):
model = Message
template_name = 'message/compose.html'
form_class = MessageCreateForm
def get_initial(self):
initial = super().get_initial()
initial['sender'] = self.request.user
return initial
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
user = self.request.user
followers = user.followers.values_list('user_id', flat=True)
follower_objects = []
kwargs['user'] = self.request.user
kwargs['follower_objects'] = follower_objects
for id in followers:
follower = User.objects.get(id=id)
follower_objects.append(follower)
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
You have to use forms.ModelChoiceField instead of forms.ChoiceField
ForeignKey (model) > ModelChoiceField (form) - Default widget: Select
ModelChoiceField has attribute queryset.
You can filter field reciever.queryset directly in MessageCreateForm.__init__ method.
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'].queryset = user.followers
UPDATE:
You can set a custom ModelChoiceField that will return any label you want (more info).
from django.forms import ModelChoiceField
class RecieverModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.username
or
def __init__(self, *args, **kwargs):
....
self.fields['reciever'].label_from_instance = lambda obj: "%s" % obj.username
In my ModelForm, I am filtering the project_users to a certain set of all Users. How can I customize the checkboxes to show a users first_name and last_name?
Currently showing their email address as the checkbox label.
models.py
class Project(models.Model):
project_business_profile = models.ForeignKey(BusinessProfile, on_delete=models.CASCADE)
project_users = models.ManyToManyField(User, related_name='project_users')
...
def __str__(self):
return str(self.project_name)
views.py
class EditProject(LoginRequiredMixin, View):
login_url = '/signin'
redirect_field_name = 'signin'
def get(self, request, project_id):
...
form = EditProjectForm(instance=project)
...
forms.py
class EditProjectForm(ModelForm):
project_users = forms.ModelMultipleChoiceField(
widget = forms.CheckboxSelectMultiple,
queryset = User.objects.none()
)
class Meta:
model = Project
fields = ['project_users']
def __init__(self, *args, **kwargs):
super(EditProjectForm, self).__init__(*args, **kwargs)
current_project = self.instance
current_business = current_project.project_business_profile
users = current_business.business_users.all()
self.fields['project_users'].queryset = current_business.business_users.all()
// Spits out the correct users however I need to access other user fields of User in template. Name etc
template
{{form.as_p}}
I'm not entirely sure I understand, do you just want to change the label which shows your user? If so something like this may work:
forms.py
class EditProjectForm(ModelForm):
project_users = forms.ModelMultipleChoiceField(
widget = forms.CheckboxSelectMultiple,
queryset = User.objects.none()
)
class Meta:
model = Project
fields = ['project_users']
def __init__(self, *args, **kwargs):
super(EditProjectForm, self).__init__(*args, **kwargs)
current_project = self.instance
current_business = current_project.project_business_profile
users = current_business.business_users.all()
self.fields['project_users'] = UserChoiceField(queryset=users)
// Spits out the correct users however I need to access other user fields of User in template. Name etc
class UserChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
user = User.objects.get(id=obj.id)
return user.get_full_name()
I have a model StaffProfile.while creating a form for Visiti want to get staff_user data(Based on current user) to that ChoiceField (to_meet).
models.py
class StaffProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
related_name="user_profile")
staff_user = models.ManyToManyField(User, null=True, blank=True,
related_name="staff_user")
class Visit(models.Model):
name = models.CharField(max_length=200, name="name")
gender = models.CharField(choices=GENDER_CHOICE, max_length=1, name="gender")
mobile = models.CharField(max_length=18, default="", name="mobile")
to_meet = models.ForeignKey(User, on_delete=models.CASCADE)
forms.py
class VisitForm(forms.ModelForm):
to_meet = forms.ChoiceField(choices=[], required=False, label="Select Staff")
class Meta:
model = Visit
fields = ("__all__")
def __init__(self, *args, **kwargs):
super(VisitForm, self).__init__(*args, **kwargs)
self.fields['to_meet'].choices = StaffProfile.objects.filter(user=request.user).values_list("staff_user")
Initially override the __init__() method of your view
class VisitForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(VisitForm, self).__init__(*args, **kwargs)
self.fields['to_meet'].choices = [self.request.user]
to_meet = forms.ChoiceField(choices=[], required=False, label="Select Staff")
class Meta:
model = Visit
fields = "__all__" # small typo here
Then, in your view,
def foo_view(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = VisitForm(request.POST,request=request)
# check whether it's valid:
if form.is_valid():
# do somrthing
.....
You need to pass the request from view to form. For example:
def some_view(request):
form = VisitForm(request=request)
# rest of the code
and use it in the form:
def __init__(self, *args, **kwargs):
request = kwargs.pop('request')
super(VisitForm, self).__init__(*args, **kwargs)
self.fields['to_meet'].choices = StaffProfile.objects.filter(user=request.user).values_list("staff_user")
I have a form where I am trying to limit the choices which appear in the field 'question' (In this case, I only want questions which the user has created). This method has worked with other forms, but it doesn't work this time - probably because it is a ModelFormset rather than just a ModelForm
The exact error is - 'NoneType' object has no attribute 'username', which I suspect means that the 'user' object is not being passed to AnswerForm's constructor. Problem is, I have no idea why it's not being passed
#views.py
def add_answer(request):
a_form = modelformset_factory(Answer, form=AnswerForm(user=request.user), fields='__all__', min_num=2, max_num=4, validate_min=True)
if request.method == "POST":
form = a_form(request.POST)
if form.is_valid():
#Do something
return render(request, 'site/addanswer.html', {'a_form': a_form})
#forms.py
class AnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['question'] = forms.ModelChoiceField(queryset=Question.objects.filter(user__username=self.user.username))
class Meta:
model = Answer
fields = ('question', 'answer', 'correct')
#models.py
class Question(models.Model):
user = models.ManyToManyField(User)
test = models.ForeignKey(Test, on_delete=models.CASCADE)
question = models.TextField(max_length=1000)
def __str__(self):
return "{0}".format(self.question)
class Answer(models.Model):
question = models.ForeignKey(Question)
answer = models.TextField(max_length=1000)
correct = models.BooleanField()
def __str__(self):
return "{0}".format(self.answer)
Maybe self.user get overwritten by super. Also there is no need to query user.username.
#forms.py
class AnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['question'] = forms.ModelChoiceField(queryset=Question.objects.filter(user=user))
class Meta:
model = Answer
fields = ('question', 'answer', 'correct')
I have two models (OK 3 models since AssignedAsset is a subclass of Asset), one that tracks assets and another that tracks the history of owners for that asset. When I create a new asset using CreatView I would like to automatically have it create a History record as well.
models.py
class Asset(models.Model):
make = models.CharField(max_length=100)
model = models.CharField(max_length=100)
serial_number = models.CharField(max_length=100)
po = models.ForeignKey('purchaseorders.PurchaseOrder', default=None, blank=True, null=True)
location = models.ForeignKey('locations.Plant')
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.make}-{0.model}-{0.serial_number}".format(self)
self.slug = slugify(forslug)
super(Asset, self).save()
class AssignedAsset(Asset):
user = models.ForeignKey(User)
def __str__(self):
return self.slug
class AssignedHistory(models.Model):
assset = models.ForeignKey('Asset')
user = models.ForeignKey(User)
date = models.DateField()
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.asset}-{0.date}".format(self)
self.slug = slugify(forslug)
super(AssignedHistory, self).save()
Here is my view.
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
success_url = '/assets'
And my forms.py
class AssignedAssetForm(forms.ModelForm):
class Meta:
model = AssignedAsset
fields = ['make', 'model', 'serial_number', 'location', 'user', 'po']
def __init__(self, *args, **kwargs):
super(AssignedAssetForm, self).__init__(*args, **kwargs)
#Filter out PO's that have packingslips (otherwise you will quickly have a ridicously big drop-down of every PO in the system)
self.fields['po'] = forms.ModelChoiceField(required=False, queryset=PurchaseOrder.objects.filter(packing_slip=''))
I thought maybe I could have it create the history when it gets the success URL, so I tried this in my view:
import time
def today():
return time.strftime ("%m/%d/%Y")
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
def get_success_url(self):
history = AssignedHistory.objects.create(assset=self.object, user=self.object.user, date=today())
return '/assets'
But this throws a TypeError:
save() got an unexpected keyword argument 'force_insert'
Anything that would point me in the right direction would be appreciated.
You can do it at multiple levels(DB level, form level).
In your case, I'll say you just need to override the save() of your AssignedAssetForm. (Assuming you set user in context of form)
def save(self, *args, **kwargs):
assigned_asset = super(AssignedAssetForm, self).save(*args, **kwargs)
user = self.context.get(u'user')
if user:
assigned_asset_history = AssignedHistory(asset=assigned_asset, user=user, date=datetime.date.today())
assigned_asset_history.save()
return assigned_asset
** I am not sure about the context part, you may have to look into how to use user in form.
You should write your Asset.save() and AssignedHistory.save() as:
def save(self, **kwargs):
...
super(YourModel, self).save(**kwargs)
...
Note the **kwargs. They allow you to accept optional parameters (and a Model.save() has a few).