There are many answers concerning this topic. None of them which I read, helped me to find a way out.
Situation: I've a contact model and I wish to assign an email address. They reside in a table with a foreign key.
Now, I'd like to add a button: "add e-mail" which opens a form. As I am in the detail view of the contact, I'd like to hand the id over into the form to either pre- or post-populate the email object (fk to contact) with the contact pk.
The easiest way would from my point of view be to just add the pk to the url and use it in "form_valid".
The view:
class EmailCreateView(LoginRequiredMixin, CreateView):
model = EmailAddress
fields = ('scope', 'email',) # 'contact',
template_name = 'contacts/contact_form.html'
# initial = {'email': 'test'}
def form_valid(self, form):
form.instance.create_by = self.request.user.username
form.instance.update_by = self.request.user.username
# form.instance.contact = Contact.objects.get(self.kwargs['contact_pk']) <- not working
return super().form_valid(form)
The urlpattern:
path('email/add/', EmailCreateView.as_view(), name='add_email'),
The model:
class EmailAddress(CommonInfo):
scope = models.CharField('Bereich', max_length=2, choices=SCOPE_TYPES)
contact = models.ForeignKey(Contact, verbose_name='Kontakt', on_delete=models.CASCADE)
email = models.EmailField('E-Mail')
def __str__(self):
return '{}'.format(self.email)
def get_absolute_url(self):
return reverse('contacts:detail', kwargs={'pk': self.contact_id})
I really have no idea what I'm missing. As this is a standard case in DB development, it ought to be easy. I'm almost certain that I've overlooked something simple. But whenever I try to pass a url parameter over, I run into a NoReverseMatch error.
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.create_by = self.request.user.username
self.object.update_by = self.request.user.username
self.object.save()
return super().form_valid(form)
Related
I'm used to collecting the current logged in user in a CreateView and passing it to the form like so:
class MakeFantasyTeam(CreateView):
form_class = MakeFantasyTeamForm
[...]
def form_valid(self, form):
form.instance.team_manager = self.request.user
form.save()
return super(MakeFantasyTeam, self).form_valid(form)
However, this doesn't seem to work when using an InlineFormSetView as provided by django-extra-views. I get an error NOT NULL constraint failed: tournament_invite.invited_by_id and I'm not sure how to get the user.id passed on to the form.
My View:
class InvitePlayersView(InlineFormSetView):
template_name = 'invite_players.html'
model = Tournament
inline_model = Invite
form_class = InvitePlayerForm
pk_url_kwarg = 'tourney_id'
factory_kwargs = {'can_delete': False, 'extra': 1}
def formset_valid(self, formset):
tourney_id = self.kwargs['tourney_id']
formset.instance.invited_for = Tournament.objects.filter(id=tourney_id).get()
formset.instance.invited_by = self.request.user
formset.save()
return super(InvitePlayersView, self).formset_valid(formset)
def get_success_url(self):
return reverse('make_team', kwargs={'tourney_id': self.object.invited_for.id})
My Model:
class Invite(models.Model):
name = models.CharField(max_length=200, blank=True, null=True)
email = models.CharField(max_length=320, null=False, blank=False, validators=[EmailValidator],)
invited_by = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING)
invited_for = models.ForeignKey(Tournament, on_delete=models.DO_NOTHING)
created_dt = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
def get_absolute_url(self):
return reverse('home')
My Form:
class InvitePlayerForm(forms.ModelForm):
class Meta:
model = Invite
fields = ('name', 'email',)
Any tips or hints much appreciated!
Thank you,
Jon
Edit: Just to clarify what I'm trying to do here; I want a user to submit a formset. The data of that formset should be stored in the model, and the userid of the submitting user should also be stored in the model. I don't seem to be able to pass on the userid though.
I am not sure what you exactly want to do here, As per my understanding you want to use the currently logged in user's information. To do so you can append the user's info in the session dictionary. After that you can use the information in templates or in other views too.
In authentication view
def login(request):
#your necessary data
request.session['user_id']=The_user_id
request.session['user_name']=The_userName
To access data in the template
{% request.session.user_id %}
{% request.session.user_name %}
To access data in other views
def myview(request):
user_id= request.session['user_id']
user_name= request.session['user_name']
I'm using django's CreateView to add images to a book. I pass the book's id to the class based view as a parameter in the url. Form fields such as book and language are not rendered on the template, rather they're obtained with the help of the book's id.
# views.py
class PictureCreateView(CreateView):
model = Upload
fields = "__all__"
book_id = None
def get_initial(self):
initial = super(PictureCreateView, self).get_initial()
initial = initial.copy()
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
initial['book'] = book
initial['language'] = language
initial['uploader'] = self.request.user
return initial
# set book_id so it used in the template
def get_context_data(self, **kwargs):
context = super(PictureCreateView, self).get_context_data(**kwargs)
context['book_id'] = self.book_id
return context
def form_valid(self, form, **kwargs):
print('Form is valid')
self.object = form.save()
files = [serialize(self.object)]
data = {'files': files}
response = JSONResponse(data, mimetype=response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(PictureCreateView, self).form_valid(form)
def form_invalid(self, form):
print('Form invalid!')
print(form.errors)
data = json.dumps(form.errors)
return HttpResponse(content=data, status=400, content_type='application/json')
# models.py
class Upload(models.Model):
image = models.ImageField(upload_to=get_upload_path, help_text='Image to process')
uploader = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, related_name='uploader')
language = models.ForeignKey(Language, models.CASCADE)
book = models.ForeignKey(Book, models.CASCADE)
The problem is that I get an error saying the form is invalid, and the fields uploader, book and language are required. How do I resolve this?
The initial data is used to display the defaults when the form is initially displayed. It isn't used when those values are missing from the submitted form data. If fields like book and uploader are set from the URL or logged-in user, then you should leave them out of the form completely, instead of setting them in the initial data. You can then set the values on the instance in the form_valid method before the form is saved.
from django.contrib.auth.mixins import LoginRequiredMixin
class PictureCreateView(LoginRequiredMixin, CreateView):
model = Upload
fields = ['other_field1', 'other_field2', ...] # leave out book, language and uploader
def form_valid(self, form):
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
form.instance.book = book
form.instance.language = ????
form.instance.uploader = self.request.user
return super(
The LoginRequiredMixin makes sure that only logged-in users can access the view.
You may want to use get_object_or_404 to handle the case where book_id refers to a book that does not exist.
One thought, initial doesn't fill the model for submission. You need to do that in init
def __init__(self):
super(PictureCreateView, self).__init__()
self.fields['book'] = self.initial['book']
self.fields['uploader'] = self.initial['uploader']
self.fields['language'] = self.initial['book']
Or, if you don't want to set the fields, make sure they are optional in your original model:
class Upload(models.Model):
uploader = models.ForeignKey('uploader', on_delete=models.CASCADE, null=True, blank=True)
book = models.ForeignKey('book', on_delete=models.CASCADE, null=True, blank=True)
language = models.ForeignKey('language', on_delete=models.CASCADE, null=True, blank=True)
I'm making an election information app, and I want to allow the currently logged-in user to be able to declare himself and only himself as a candidate in an election.
I'm using Django's built-in ModelForm and CreateView. My problem is that the Run for Office form (in other words, the 'create candidate' form) allows the user to select any user in the database to make a candidate.
I want the user field in the Run for Office to be automatically set to the currently logged-in user, and for this value to be hidden, so the logged-in user cannot change the value of the field to someone else.
views.py
class CandidateCreateView(CreateView):
model = Candidate
form_class = CandidateForm
template_name = 'candidate_create.html'
def form_valid(self, form):
f = form.save(commit=False)
f.save()
return super(CandidateCreateView, self).form_valid(form)
forms.py
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
models.py
class Candidate(models.Model):
user = models.ForeignKey(UserProfile)
office = models.ForeignKey(Office)
election = models.ForeignKey(Election)
description = models.TextField()
def __unicode__(self):
return unicode(self.user)
def get_absolute_url(self):
return reverse('candidate_detail', kwargs={'pk': str(self.id)})
Remove user field from rendered form (using exclude or fields, https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#selecting-the-fields-to-use )
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
exclude = ["user"]
Find user profile and set user field in the create view.
class CandidateCreateView(CreateView):
...
def form_valid(self, form):
candidate = form.save(commit=False)
candidate.user = UserProfile.objects.get(user=self.request.user) # use your own profile here
candidate.save()
return HttpResponseRedirect(self.get_success_url())
Assumptions
We don't want to set null=True becaues we don't want to allow null users at the model and/or database level
We don't want to set blank=True to mess with the readability of model because the user actually will not be blank
#nbm.ten solution is a good one. It has an advantages over other 'solutions'to this problem that utilized model to set the user (like this one) in nbm.ten's doesn't undermine the assumptions above. We don't want to mess with the model to fix a problem in view!
But here I add two other solutions based on django documentation (Models and request.user):
Two other solutions
1. Using the generic CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Candidate
class CandidateCreate(LoginRequiredMixin, CreateView):
model = Candidate
exclude = ['user']
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
2. Using class-based views
class CandidateForm(ModelForm):
class Meta:
model = Candidate
exclude = [ 'user',]
class CandidateAddView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
form = CandidateForm()
context = {'form':form}
return render(request, 'myapp/addcandidateview.html', context)
def post(self, request, *args, **kwargs):
form = CandidateForm(request.POST)
form.instance.user = request.user
if form.is_valid():
form.save()
return redirect(reverse('myapp:index'))
NOTES
Note that LoginRequiredMixin prevents users who aren’t logged in from accessing the form. If we omit that, we'll need to handle unauthorized users in form_valid() or post().
Also exclude = ['user'] prevents the user field to be shown on the form.
We used form.instance.user to set the user not form.data or form.cleaned_data they don't work
I am looking for a simple answer by example to this common problem. The answers I found so far leave out critical points for us beginners.
I have an app where almost every model has a ForeignKey to User, and there is a unique_together constraint, where one of the fields is always 'user'.
For example:
class SubscriberList(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=70)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (
('user', 'name',),
)
def __unicode__(self):
return self.name
A SubscriberList is always created by a logged in User, and thus in the form to create a Subscriber List, I exclude the user field and give it a value of self.request.user when saving the form, like so:
class SubscriberListCreateView(AuthCreateView):
model = SubscriberList
template_name = "forms/app.html"
form_class = SubscriberListForm
success_url = "/app/lists/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
return super(SubscriberListCreateView, self).form_valid(form)
And here is the accompanying form:
class SubscriberListForm(ModelForm):
class Meta:
model = SubscriberList
exclude = ('user')
With this code, valid data is fine. When I submit data that is not unique_together, I get an Integrity Error from the database. The reason is clear to me - Django doesn't validate the unique_together because the 'user' field is excluded.
How do I change my existing code, still using CreateView, so that submitted data that is not unique_together throws a form validation error, and not an Integrity Error from the db.
Yehonatan's example got me there, but I had to call the messages from within the ValidationError of form_valid, rather than a separate form_invalid function.
This works:
class SubscriberCreateView(AuthCreateView):
model = Subscriber
template_name = "forms/app.html"
form_class = SubscriberForm
success_url = "/app/subscribers/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
try:
self.object.full_clean()
except ValidationError:
#raise ValidationError("No can do, you have used this name before!")
#return self.form_invalid(form)
from django.forms.util import ErrorList
form._errors["email"] = ErrorList([u"You already have an email with that name man."])
return super(SubscriberCreateView, self).form_invalid(form)
return super(SubscriberCreateView, self).form_valid(form)
Taking from the docs at:
https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects
You should only need to call a model’s full_clean() method if you plan to handle validation errors yourself, or if you have excluded fields from the ModelForm that require validation.
Taking from the docs at:
https://docs.djangoproject.com/en/dev/ref/class-based-views/#formmixin
Views mixing FormMixin must provide an implementation of form_valid() and form_invalid().
This means that in order to view the error (which isn't form related) you'll need to implement your own form_invalid, add the special error message there, and return it.
So, running a full_clean() on your object should raise the unique_together error, so your code could look like this:
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
# validate unique_together constraint
try:
self.object.full_clean()
except ValidationError:
# here you can return the same view with error messages
# e.g.
return self.form_invalid(form)
return super(SubscriberListCreateView, self).form_valid(form)
def form_invalid(self, form):
# using messages
# from django.contrib import messages
# messages.error('You already have a list with that name')
# or adding a custom error
from django.forms.util import ErrorList
form._errors["name"] = ErrorList([u"You already have a list with that name"])
return super(SubscriberListCreateView, self).form_invalid(form)
HTH
adding another example that might be a bit easier for noobs.
forms.py
class GroupItemForm(ModelForm):
def form_valid(self):
self.object = self.save(commit=False)
try:
self.object.full_clean()
except ValidationError:
# here you can return the same view with error messages
# e.g. field level error or...
self._errors["sku"] = self.error_class([u"You already have an email with that name."])
# ... form level error
self.errors['__all__'] = self.error_class(["error msg"]
return False
return True
views.py
def add_stock_item_detail(request, item_id, form_class=GroupItemForm, template_name="myapp/mytemplate.html"):
item = get_object_or_404(Item, pk=item_id)
product = Product(item=item)
if request.method == 'POST':
form = form_class(request.POST, instance=product)
if form.is_valid() and form.form_valid():
form.save()
return HttpResponseRedirect('someurl')
else:
form = form_class(instance=product)
ctx.update({
"form" : form,
})
return render_to_response(template_name, RequestContext(request, ctx))
I have a form that edits an instance of my model. I would like to use the form to pass all the values as hidden with an inital values of username defaulting to the logged in user so that it becomes a subscribe form. The problem is that the normal initial={'field':value} doesn't seem to work for manytomany fields. how do i go about it?
my views.py
#login_required
def event_view(request,eventID):
user = UserProfile.objects.get(pk=request.session['_auth_user_id'])
event = events.objects.get(eventID = eventID)
if request.method == 'POST':
form = eventsSusbcribeForm( request.POST,instance=event)
if form.is_valid():
form.save()
return HttpResponseRedirect('/events/')
else:
form = eventsSusbcribeForm(instance=event)
return render_to_response('event_view.html', {'user':user,'event':event, 'form':form},context_instance = RequestContext( request ))
my forms.py
class eventsSusbcribeForm(forms.ModelForm):
eventposter = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.HiddenInput())
details = forms.CharField(widget=forms.Textarea(attrs={'cols':'50', 'rows':'5'}),label='Enter Event Description here')
date = forms.DateField(widget=SelectDateWidget())
class Meta:
model = events
exclude = ('deleted')
def __init__(self, *args, **kwargs):
super(eventsSusbcribeForm, self).__init__(*args, **kwargs)
self.fields['username'].initial = (user.id for user in UserProfile.objects.filter())
my models.py
class events(models.Model):
eventName = models.CharField(max_length=100)
eventID = models.AutoField(primary_key=True)
details = models.TextField()
attendanceFee = models.FloatField(max_length=99)
date = models.DateField()
username = models.ManyToManyField(UserProfile, related_name='user', blank=True)
eventposter = models.ForeignKey(UserProfile, related_name='event_poster')
deleted = models.BooleanField()
def __unicode__(self):
return u'%s' % (self.eventName)
Can you post your Event model? It's too hard to guess what you are trying to do without that. I have to assume a few things without it, so I'm sorry if I'm wrong.
First off, I'm guessing that you should not be using an Event ModelForm for the EventSubscriptionForm. That doesn't really make sense. Hopefully, you created a through class for Event and User, so in your Event model, you have something like
subscriber_users = models.ManyToManyField(User, through="Subscription")
and
class Subscription(models.Model):
user = models.ForeignKey(User, related_name="events",)
event = models.ForeignKey(Event, related_name="subscribers")
Then you can use a Subscription ModelForm.
Is there any reason you're using eventID instead of the django idiom, event_id? You should also import your Event and EventSubcribeForm classes with Pythonic casing. One very important thing is that you should be linking everything to User and not UserProfile.
Technically, it makes more sense to set initial in the view rather than the form init, because you would have to pass request.user to init anyway.
I think you should try this for your view...
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user}
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))
A few notes
use request.user.get_profile() for the current user's profile object
you can use request.POST or None to avoid the request.method cases
always use named urls so you can reverse on names instead of hard-coding urls into views
if you want user in your template context, just setup a context processor (see pinax for example on how to do this) instead of passing it in every single view. You can always use request.user also.
Keep in mind that this code will only work if you have that through class setup like I said and you use a form like
class EventSubcriptionForm(forms.ModelForm):
class Meta:
model = Subscription
exclude = ('event')
EDIT
Thanks a bunch for the ups. I'm not new to django, but somehow very new to SO.
Okay, you should really read some of the PEPs about Python conventions http://www.python.org/dev/peps/pep-0008/ or some SO posts about it What is the naming convention in Python for variable and function names?.
Here's what I recommend for your event app models.py:
class Event(models.Model):
name = models.CharField(max_length=100)
details = models.TextField()
attendance_fee = models.FloatField(max_length=99)
date = models.DateField()
poster = models.ForeignKey(User, related_name='events_posted')
deleted = models.BooleanField()
attendee_users = models.ManyToManyField(User, through="Attendance")
def __unicode__(self):
return self.name
class Attendance(models.Model):
user = models.ForeignKey(User, related_name="events",)
event = models.ForeignKey(Event, related_name="attendees")
Notes
The name of a class is capitalized and singular. You are not describing events, you are the blueprint for an Event.
you never need the name of the class in its attributes, i.e. event_name can just be name.
all variables are lowercase_and_underscored
always link to User, not your profile model. A lot of django code expects this.
So now you can access the users attending the event with event.attendees.
I found this while trying to set defaults for the manytomany. I didn't want to add a through table.
based on the view Casey posted, but adding the user in the manytomany relation.
for the initial post:
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user, 'username': [ request.user.id, ] } # makes the poster also an attendee
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))
updated version:
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user, 'subscriber_users': [ request.user.id, ] } # makes the poster also an subscriber
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))