I'm building a django web app to allow customers to sign up for courses. On the page "training" all courses are listed. The users clicks on a course to get more details and apply for that course. The course model looks like this:
class Course(models.Model):
course_text = models.CharField(max_length=200)
slug = models.SlugField()
leader_text = models.TextField()
location = models.CharField(max_length=50)
start_date = models.DateField('start date')
duration_days = models.CharField (max_length=1)
cost = models.DecimalField(max_digits=7, decimal_places=2)
available = models.CharField(max_length=2)
class Meta:
ordering = ['start_date']
I have an application model "Applicant" that looks like this:
class Applicant(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(max_length=75)
contact_date = models.DateTimeField(auto_now=True)
courses = models.ManyToManyField(Course)
When the user click on a course the details of that course are rendered in the view and a ModelForm is also rendered for the application process. The view looks like this:
def view_course(request, slug):
print Course.objects.all() #Just for testing
if request.method == "POST":
form = ApplicantForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.save()
return HttpResponseRedirect('/thanks/')
else:
form = ApplicantForm()
return render(request, 'training/view_course.html', {'course': get_object_or_404(Course, slug=slug), 'form': form})
All of the above works and I can view the users in my django admin as they apply. What I would like to do is gather the specific information of the course they are applying for and tie that to each user. I was hoping that the ManyToMany relationship on the "Course" model would help but if I include it in the rendered form it just gives options to choose any course.
I'm getting somewhere with querysets such as Course.objects.all()
but I need to filter that on the current values from "Course" in the view. Is there a current command or similar to get the data that's held in the current session?
I'm using Django 1.7 and MySQL as the database.
Clarifications
I want to display the minimum on the form the user fills in to ease the application process. Therefore only first_name, last_name and email are required. I want this information to be linked to the Course the user is currently viewing so those values are automatically completed and not seen by the user.
As for many applicant instances in the db isn't that how ManyToMany relationships work? In my admin page I want to be able to click on a users and see the courses they're signed up for and also be able to click on a course to see how many users ave applied. My admin page is nowhere near that yet but I wanted to be able to get the current information saved before worrying about that. Here's what admin looks like:
from django.contrib import admin
from training.models import Course, Applicant
class CourseAdmin (admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['course_text', 'location', 'start_date', 'duration_days', 'cost', 'available']}),
('Course details', {'fields': ['leader_text'], 'classes': ['collapse']}),
]
list_display = ['course_text', 'location','start_date', 'was_published_recently']
list_filter = ['start_date', 'location']
search_fields = ['course_text']
admin.site.register(Course, CourseAdmin)
class ApplicantAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['first_name', 'last_name', 'email',]}),
]
list_display = ['first_name', 'last_name','email',]
list_filter = ['first_name', 'last_name']
search_fields = ['courses']
admin.site.register(Applicant, ApplicantAdmin)
A Solution of Sorts
First up thanks to spookylukey. Exactly right that I had two things going on and indeed it could become problematic having the same user creating more than one applicant object. I was trying to get away from the auth/auth idea as I think it leads to a lot of drop-offs. I see now why it's important.
That aside spookylukey's suggestion of using the Model.ManyToManyField.add()
method worked. The code for the view.py is now:
def view_course(request, slug):
courseId = Course.objects.get(slug=slug)
if request.method == "POST":
form = ApplicantForm(request.POST)
if form.is_valid():
applicant = form.save(commit=False)
applicant.save()
form.save_m2m()
#The add method working it's magic
applicant.courses.add(courseId)
return HttpResponseRedirect('/thanks/')
else:
form = ApplicantForm()
#print request.session['course' : get_object_or_404(Course, slug=slug)]
return render(request, 'training/view_course.html', {'course': get_object_or_404(Course, slug=slug), 'form': form})
the additional problem of viewing the relationship in the admin interface was solved by using InlineModelAdmin Objects. So the admin.py looks like this:
class SignupsInline(admin.TabularInline):
model = Applicant.courses.through
class CourseAdmin (admin.ModelAdmin):
#other styling goes here...
inlines = [
SignupsInline,
]
admin.site.register(Course, CourseAdmin)
class ApplicantAdmin(admin.ModelAdmin):
#other styling goes here...
inlines = [
SignupsInline,
]
admin.site.register(Applicant, ApplicantAdmin)
To get more information about the associated applicant object (in the course admin section) or the course object (in the applicant admin section) I added the following function to the Applicant model (and a corresponding one to the Course model):
def __unicode__(self):
return u'%s %s %s' % (self.first_name, self.last_name, self.email)
Not great but serves its purpose for the time being.
It sounds like you have two things going on:
creating Applicant
records, which I presume you would only want to do once per user - if a user wants to apply for more than one course, you don't want to prompt for all their details again.
associating an Applicant
with a Course
.
So I think you need a more complex workflow - one that stores the created Applicant
data or id in the session, and re-uses as needed, or prompts for more data.
Whether or not you do this, the way to fix your view is to stop thinking about trying to use a ModelForm
for everything. The view_course
view is not a view that allows a person to edit everything about their Applicant
record - it doesn't allow them to set/unset their entire list of courses. So using a ModelForm
to set courses
is not appropriate. Instead, you need to use the Applicant
API that has been created for you by the ORM.
In your view code, I presume you have something like:
course = Course.objects.get(slug=slug)
You will need to add this course
to the applicant:
applicant = form.save()
applicant.courses.add(course)
The applicant
object will either be retrieved from the return value of form.save()
, or from the session as I mentioned above.