Class-based-views - Pulling together multiple models using FormView - django

I am trying to build a Survey App. I have defined a model for Survey, Questions, Responses as below.
What I want to do is create a Survey (in admin, which I can do) then display this Survey as a page which users can fill in.
I'm using Class Based Views for the first time and I'm stuck with how to render the Survey form along with the associated Questions and allow input of Answers.
In Admin, I've created a Survey and successfully added Questions to it. FormView (and UpdateView, tried as part of another SO answer) allows me to display the Survey model's 'name' and 'description' attributes - but the questions don't appear.
What do I need to do to make the Survey and it's Questions available in my form (and allow the user to enter a response)?
MODELS
class Survey(models.Model):
name = models.CharField(max_length=400)
description = models.TextField()
def survey_questions(self):
if self.pk:
return Question.objects.filter(survey=self.pk)
else:
return None
def publish(self):
self.published_date = timezone.now()
self.save()
class Question(models.Model):
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
question = models.TextField()
class Response(models.Model):
member = models.ForeignKey(user_model, on_delete=models.SET_NULL, null=True, blank=True)
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now_add=True)
class AnswerBase(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
response = models.ForeignKey(Response, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class AnswerText(AnswerBase):
body = models.TextField(blank=True, null=True)
URLS
urlpatterns = [path('survey/<int:pk>/', views.SurveyResponseView.as_view(), name='survey_detail')]
VIEWS
class SurveyResponseView(FormView):
template_name = 'survey/survey_detail.html'
form_class = ResponseForm
FORMS
class ResponseForm(forms.ModelForm):
class Meta():
model = Survey
fields = '__all__'

Okay so the way to do this is to use FormSets. They're a bit of a faff to setup, but shouldn't take you too long. I recommend using this small js library to help.
I also rewrote some of your code to hopefully make life a bit easier for you:
models.py
class SurveyTemplate(models.Model):
name = models.CharField(max_length=400)
description = models.TextField()
# You were missing this I believe
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
class Question(models.Model):
# Now you can do SurveyTemplate.questions.all()
survey = models.ForeignKey(Survey, on_delete=models.CASCADE, related_name='questions')
question = models.TextField()
class SurveyResponse(models.Model):
member = models.ForeignKey(user_model, on_delete=models.SET_NULL, null=True, blank=True)
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now_add=True)
class Answer(models.Model):
# Now you can do Question.answers.all()
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='answers')
# Now you can do Response.answers.all()
response = models.ForeignKey(Response, on_delete=models.CASCADE, related_name='answers')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
views.py
from django.views.generic import CreateView
# CreateView is Django's built in View for creating an object, you would be best to use it
class SurveyResponseView(CreateView):
model = Survey
template = 'survey/survey_detail.html'
form_class = ResponseForm
forms.py
class ResponseForm(forms.ModelForm):
class Meta():
model = Survey
fields = '__all__'

Related

Query with multiple foreign keys (django)

I'm making a searchbar for a site I'm working on and I'm having trouble when I want to filter different fields from different models (related between them) Here are my models:
class Project(models.Model):
name = models.CharField(max_length=250)
objective = models.CharField(max_length=250)
description = models.TextField()
launching = models.CharField(max_length=100, null=True, blank=True)
image = models.ImageField(
upload_to='imgs/', null=True, blank=True)
image_thumbnail = models.ImageField(
upload_to='thumbnails/', null=True, blank=True)
slug = models.CharField(max_length=250)
class Meta:
db_table = 'project'
def __str__(self):
return self.name
class Institution(models.Model):
name = models.CharField(max_length=250)
project = models.ManyToManyField(Proyecto)
class Meta:
db_table = 'institution'
def __str__(self):
return self.name
And I want to be able to search by the name of the project or the institution, but my code only takes the institution's name.
def searchbar(request):
if request.method == 'GET':
search = request.GET.get('search')
post = Project.objects.all().filter(name__icontains=search, institution__name__icontains=search)
return render(request, 'searchbar.html', {'post': post, 'search': search})
How can I search for all the projects that match by its name OR the institution's name?
BTW, I'm using SQL, not sure if it's relevant, but I thought I should add that info.
You can .filter(…) [Django-doc] with Q objects [Django-doc]:
from django.db.models import Q
Project.objects.filter(Q(name__icontains=search) | Q(institution__name__icontains=search))
or you can work with the _connector parameter:
from django.db.models import Q
Project.objects.filter(
name__icontains=search,
institution__name__icontains=search,
_connector=Q.OR
)

How to add Category while adding product in Django form? Just like the Django admin form

So I am trying to build an inventory system.
I have 2 models, Categories and Product connected through the ManyToMany field.
I want to add a category while I am adding the product just like it happens in the Django admin form.
How can I do that?
My model.py File
class Categories(models.Model):
name = models.CharField(max_length=30)
organisation = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
def __str__(self):
return f"{self.name}"
class Product(models.Model):
category = models.ManyToManyField(Categories)
brand = models.CharField(max_length=20)
model = models.CharField(max_length=20)
hac = models.CharField(max_length=20, null=True, blank=True)
rate = models.DecimalField(max_digits=6, decimal_places=2)
stock = models.IntegerField(default=0)
# organisation = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.brand} {self.model} "
My form.py file
class ProductModelForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
**My code output **
See the below screenshots to understand what I want to do. I basically want that plus button option to add a category from the product form itself.
You should show snippets of codes that we need to provide an answer.

Django inline formset with manytomany fields

Ive spent a fair bit of time searching on this subject without finding some real up to date answers. I'm trying to create a form that creates a db entry. The basic idea is this:
Many events can have many people
So, the struggle here is that the user needs to create an event where the user can select all the people that attend. Each person that attends though, has certain things that also needs to be tracked per event. See the model below:
models.py
from django.db import models
from django.contrib.auth.models import User[]
class PersonRole(models.Model):
role = models.CharField(max_length=20, choices=ROLE_CHOICES, unique=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.role
class PersonClass(models.Model):
name = models.CharField(max_length=100, choices=CLASS_CHOICES, unique=True)
color = models.CharField(max_length=6, choices=COLOR_CHOICES, unique=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Person(models.Model):
name = models.CharField(max_length=100, unique=True)
personclass = models.ForeignKey(PersonClass, on_delete=models.CASCADE, blank=True, null=True)
personrole = models.ForeignKey(PersonRole, on_delete=models.CASCADE, blank=True, null=True)
value = models.IntegerField(default=0)
reliability = models.IntegerField(default=0)
last_item = models.DateField(auto_now=False, blank=True, null=True)
last_event_attended = models.DateField(auto_now=False, blank=True, null=True)
last_manager_attended = models.DateField(auto_now=False, blank=True, null=True)
item_received = models.BooleanField(default=False)
note = models.TextField(null=True, blank=True)
core_attendee = models.BooleanField(default=False)
enabled = models.BooleanField(default=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Location(models.Model):
name = models.CharField(max_length=100, unique=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Boss(models.Model):
name = models.CharField(max_length=100, unique=True)
location = models.ForeignKey(Location, on_delete=models.CASCADE)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
class Raid(models.Model):
date = models.DateTimeField(auto_now_add=True)
boss = models.ForeignKey(Boss, on_delete=models.CASCADE, null=True, blank=True)
success = models.BooleanField()
attendees = models.ManyToManyField(Person)
created_by = models.ForeignKey(User,
related_name="raids", blank=True, null=True,
on_delete=models.SET_NULL)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return str(self.date)
I've started down the path of just trying to use the generic in-built create\update\delete views and ran into this:
ValueError: 'roster.Person' has no ForeignKey to 'roster.Raid'.
forms.py
class RaidGenericCreateModelForm(forms.ModelForm):
class Meta:
model = Person
exclude = ()
RaidPersonFormSet = inlineformset_factory(Raid, Person, fields=['name', 'personclass', 'personrole', 'item_received'], extra=1, can_delete=False)
views.py
class RaidCreate(CreateView):
model = Raid
template_name = 'roster/raid_create.html'
form_class = RaidGenericCreateModelForm
success_url = None
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
person_form = RaidPersonFormSet
return self.render_to_response(
self.get_context_data(form=form,
person_form=person_form
)
)
There are 9-year old posts that say you cannot use inlineformset_factory with many to many fields. So my question here is, what are my options? What is the best way to go about simply creating an Event (referred to as Raid in the model) and at the same time selecting the people from the roster (referred to as Person in the model) and changing the options those people have associated to them for that event?
As an example of what I am trying to accomplish here:
Event 1
-Person A (selected, item_received=True)
-Person B (selected, item_received=False)
-Person C (selected, item_received=False)
-Person D (not selected, item_received=False)
Event 2
-Person A (selected, item_received=False)
-Person B (not selected, item_received=False)
-Person C (selected, item_received=True)
-Person D (selected, item_received=False)
Where the list of persons is showing all persons and some of the persons fields from the Person model.
The alternate thing you can do is use DjangoRestFramework for this purpose.
Using rest you can first send persons data to frontend then in frontend you can create Event and add person details for each event,and in last post all that data using javascript.Try it,it will surely work.

Django user model, backward look up is not working

I've made a foreign key relationship with django User model, the forward lookup is working fine but when I try to backward is throwing this error:
'QuerySet' object has no attribute 'urlpost_set'
I have also tried the related name! Also note that the Catagory to PostUrl and PostUrl to Catagory is working just fine!
My models.py:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Catagory(models.Model):
title = models.CharField(max_length=15, unique=True)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = 'catagory'
class UrlPost(models.Model):
STATUS_CHOICES = (
('public', 'Public'),
('private', 'Private'),
)
profile = models.ForeignKey(User, related_name='user_post', on_delete=models.CASCADE)
catagory = models.ForeignKey(Catagory, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
url = models.URLField()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='public')
note = models.TextField(blank=True)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
class Meta:
ordering = ['-created']
verbose_name_plural = 'url Post'
def __str__(self):
return self.title
You have set related_name='user_post' while defining ForeignKey relation between your User model and UrlPost.
You have to use .user_post.all() instead of .urlpost_set.all() in your queryset.

Create relationship between two models using django class based views

I have two models Company and Campaign. I need to create a relationship between them. I think my models are fine.
companies/model.py
class Company(models.Model):
class Meta:
verbose_name_plural = "companies"
user = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(blank=False, max_length=128, default='')
slug = models.SlugField(blank=True, unique=True)
archived = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
campaigns/models.py
class Campaign(models.Model):
class Meta:
verbose_name_plural = "campaigns"
company = models.ForeignKey('companies.Company', on_delete=models.CASCADE,)
title = models.CharField(blank=False, max_length=128, default='')
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
def __str__(self):
return str(self.title)
campaigns/forms.py
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
fields = ['title','description','archived']
campaigns/views.py
class CampaignCreateView(SubmitBtnMixin, CreateView):
model = Campaign
company = None
form_class = CampaignForm
submit_btn = "Add Campaign"
template_name = "form.html"
campaigns/urls.py
url(r'^campaign/create/$', CampaignCreateView.as_view(), name='campaign-create'),
My question is, when creating a new campaign, where and how do I pick up the Company pk to populate the Campaign model? What is the most secure and best practice for doing this?
I found a solution but would like input on best practices still.
I added this to my CampaignCreateView
def form_valid(self, form):
company = get_object_or_404(Company, id=self.kwargs.get('pk'), user_id=self.request.user.id)
form.instance.company_id = company.id
return super(CampaignCreateView, self).form_valid(form)
and I changed my url to:
url(r'^campaign/(?P<pk>\d+)/create/$', CampaignCreateView.as_view()...
Not sure that I like the pk in the URL since it can be jacked. This is why I am filtering on the userid at the company model to make sure that the data is coming from the owner.
I thought of doing this by registering the company in the session id but I am not convinced that sessions do not present their own problems.