Create relationship between two models using django class based views - django

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.

Related

Simple django filter is includes all users’ data rather than that of the logged-in user

I’m new to Django and have built a basic filter that does not filter according to the logged-in user’s data but rather all users’ data, which is incorrect. The filter is for an Automation class, which has a many:many relationship with the Message class (and funnily enough the exact same happens with the message filter).
Views.py:
#login_required(login_url='login')
#allowed_users(allowed_roles=['admin', 'customer'], own_account_only=True)
def automation_list(request, pk):
account = Account.objects.get(id=pk)
automations = account.automation_set.all()
filter = AutomationFilter(request.GET, queryset=automations)
automations = filter.qs
context = {'account': account,
'automations': automations, 'filter': filter}
return render(request, 'automations/automation_list.html', context)
Filters.py:
class AutomationFilter(django_filters.FilterSet):
start_date = DateFilter(field_name='date_joined', lookup_expr='gte')
end_date = DateFilter(field_name='date_joined', lookup_expr='lte')
class Meta:
model = Automation
fields = '__all__'
exclude = ['account', 'date_created']
Models:
class Automation(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=200)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True, null=True)
messages = models.ManyToManyField(Message, blank=True)
def __str__(self):
return self.name
class Message(models.Model):
name = models.CharField(max_length=100)
subject = models.CharField(max_length=128)
text = models.TextField()
account = models.ForeignKey(Account, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True, null=True)
automations = models.ManyToManyField('automations.Automation', blank=True)
def __str__(self):
return self.name
Why is the filter not just filtering according to the logged-in user? I’d have thought that I’m only passing in the user’s data via this line:
filter = AutomationFilter(request.GET, queryset=automations)
Thanks

Assign a model to another model using ModelChoiceField

I am new to Django and would like to create a platform where teachers can create student profiles and assign reports to the specific student.
Therefore, I have made two models (Student & Report) and two CreateViews. When creating a report, the teacher has the option to select a student from a choice field. This I have done using ModelChoiceField. However, when submitting the report model, the students name is not saved to that model. How can I accomplish this?
Ultimately I would like to have a profile to each student showing their info and reports attached to their model.
models.py
class Student(models.Model):
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
name = models.CharField(max_length=30, default="")
age = models.CharField(max_length=2, default="", blank=True)
class Report(models.Model):
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=30)
forms.py
class StudentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
class Meta:
model = Student
fields = ['name','age',]
class ReportForm(forms.ModelForm):
student = forms.ModelChoiceField(queryset=Student.name, initial=0)
class Meta:
model = Report
fields = ['title','file','player',]
views.py
class StudentCreate(LoginRequiredMixin, CreateView):
model = Student
template_name = 'student_create.html'
form_class = forms.StudentForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class ReportCreate(LoginRequiredMixin, CreateView):
model = Report
template_name = 'report_create.html'
form_class = forms.ReportForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
Currently your Report model is like so:
class Report(models.Model):
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=30)
The basic concept behind a model is that it is an abstraction / design of how we want the database table for some entity to look like. So the fields we add in a model are columns / attributes in our database table. If we want to store some data we need to have some column for it (or in terms of models a field).
Your model Report has no field for storing some student (in essence a foreign key to a Student, a foreign key is basically a pointer to a particular entry of another table). So you should add that to your model:
class Report(models.Model):
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=30)
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name="reports")

How to view advertises published by auser in his User serializer

I have user serializer in which i need to show in every user detail advertises which he published
models.py:
class Advertise(models.Model):
title = models.CharField(max_length=120)
publisher = models.ForeignKey(User, related_name='publisher',null=True, blank=True, on_delete=models.CASCADE)
category = models.CharField(choices=CATEGORIES, max_length=120)
description = models.TextField(max_length= 200, null=True, blank=True)
image = models.ImageField(upload_to='project_static/Advertise/img', null=True, blank=False)
price = models.DecimalField(decimal_places=2, max_digits=20)
timestamp = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
location = models.CharField(max_length=120 , null=True, blank=True)
contact = models.CharField(max_length=120,null=True, blank=True)
def __str__(self):
"""show ad name in admin page"""
return self.title
def get_absolute_url(self):
return reverse("advertise:advertise-detail", kwargs={"pk":self.pk})
serilaizers.py:
class AdSerializer(serializers.HyperlinkedModelSerializer):
publisher = serializers.ReadOnlyField(source='publisher.username')
url = serializers.CharField(source='get_absolute_url')
class Meta:
model = Advertise
fields = ('url','id','title','publisher','category','description','price','timestamp','approved','location','contact')
class UserSerializer(serializers.HyperlinkedModelSerializer):
publisher = AdSerializer(source='publisher_set', many=True)
class Meta:
model = User
fields = ['id', 'username','publisher']
error:
Got AttributeError when attempting to get a value for field publisher on serializer UserSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the User instance.
Original exception text was: 'User' object has no attribute 'publisher_set'.
ok, I solved it by making some changes:
class AdSerializer(serializers.HyperlinkedModelSerializer):
publisher = serializers.ReadOnlyField(source='publisher.username')
url = serializers.HyperlinkedIdentityField(view_name='advertise:ad_detailview', source='Advertise')
class Meta:
model = Advertise
fields = ('url','id','title','publisher','category','description','price','timestamp','approved','location','contact')
class UserSerializer(serializers.HyperlinkedModelSerializer):
publisher_of = AdSerializer(many=True)
url = serializers.HyperlinkedIdentityField(view_name='advertise:user-detail', source='User')
class Meta:
model = User
fields = ('url', 'id','username', 'email', 'publisher_of')
also in models.py publisher field got related_name="publisher_of" for more symantic
This link helped
https://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/

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

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__'

Django rest framework get one to many relationship data

I have a django model named Event, which references Customer model.
event_name = models.CharField(max_length=200, unique=True)
customer = models.ForeignKey(customer_models.Customer, db_index=True,
on_delete=models.SET_NULL,
related_name='customer_events', null=True)
event_location = models.CharField(max_length=200, default='')
event_date = models.DateField()
I need to get the customer list along with the latest event name for each user in the API.
Customer serializers.py file is
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = '__all__'
Customer views.py file is
class CustomerViewSet(viewsets.ModelViewSet):
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
How can I accomplish this?
In your Customer model you can have a property that returns the latest event name for a Customer instance:
class Customer(models.Model):
...
#property
def latest_event_name(self):
"""Return latest event name."""
# self.customer_events.order_by('event_date').last()
latest_event = self.customer_events.order_by('-event_date').first()
return latest_event.event_name if latest_event else None
In your serializer you can then add a ReadOnlyField for latest_event_name:
class CustomerSerializer(serializers.ModelSerializer):
latest_event_name = serializers.ReadOnlyField()
class Meta:
model = Customer
fields = '__all__'