Assign Foreign Key Value after creating ( Logged In User ) - python

I have a createview using CBV
class StudentCreate(LoginRequiredMixin, CreateView):
login_url = '/signin/'
model = Student
fields = ['first_name', 'last_name' ]
success_url = '/dashboard/'
Respective models.py
class Class_teacher(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
standard = models.IntegerField()
division = models.CharField(max_length=1)
subject = models.CharField(max_length=200)
email = models.CharField(max_length=30)
class Student(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
classteacher = models.ForeignKey('Class_teacher', on_delete=models.SET_NULL,blank=True, null=True )
The webapp has a login feature . When the user ( classteacher ) login they can add students. What I want is classteacher field in Student(model Form ) should be automatically set as user which is the classteacher. ( Classteacher ) and should be saved in the db after creating the student. Classteacher model is updated with respective required fields .

Look here for the various methods of a CreateView that you can override.
In your case, you want to override the form_valid() method, which is called when the new Student will be saved.
from django.shortcuts import get_object_or_404
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.classteacher = get_object_or_404(Class_teacher, email=self.request.user.email)
self.object.save()
return super().form_valid(form)

You need to define your own form_valid().
I assume the Teacher as a one to one Relationship with your User model.
def form_valid(self, form):
student = form.save(commit=False)
#retrieve the current logged_in teacher, of course you have to be sure this view is only accesible for teachers (in dispatch for exemple)
self.object.classteacher = self.request.user.teacher
self.object.save()
return super(StudentCreate, self).form_vaild(form)
#bonus the dispatch
def dispatch(self, request, *args, **kwargs):
#get the loged in user
if request.user.teacher:
return super(StudentCreate, self).dispatch(request, *args, **kwargs)
else:
raise Http404

Related

Django - How to include all form logic in modelForm and not in a Createview, so that form can be unittested without also testing the view?

I have a Campaign Model, and a CampaignCreateForm which is a ModelForm. The Campaign model has a contact_list field of type JSONField. When a user is creating a campaign using the CampaignCreateForm they upload a CSV file which is processed to create the JSON data for the contact_list field.
What is the best way to approach this so that I can test the form separately from the view?
I've built this using a CampaignCreateView which inherits from CreateView, and included the logic to parse the CSV file and create the JSON data in the views form_valid method, but this makes unit-testing the form (and any form field validation) impossible. I want to test the functions included in the forms clean method. With my current approach the view and form must be tested together, and that feels wrong.
How can I create the form such that all the logic for the form (processing the CSV file to create the JSON data and discarding the uploaded file) is handled in the form only?
My current CreateView and ModelForm are below:
View:
class CampaignCreateView(LoginRequiredMixin, CreateView):
model = Campaign
form_class = CampaignCreateForm # required if you want to use a custom model form, requires `model` also
template_name = "writing/campaign_create.html"
def get_success_url(self):
""" If model has get_absolute_url() defined, then success_url or get_success_url isnt neccessary
"""
user = User.objects.get(username=self.kwargs.get("username"))
return reverse("writing:campaigns", kwargs={"username": user.username})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def form_valid(self, form):
""" by default, form_valid redirects to success_url
"""
form.instance.user = self.request.user
form.instance.image_url = f"https://picsum.photos/seed/{randrange(10000)}/500/300"
file = form.cleaned_data["contact_list_file"]
file_content = file.open("r")
json_contact_list = csv_to_json(file_content)
form.instance.contact_list = json_contact_list
contact_list = json.loads(json_contact_list) # as python dict
form.instance.items = len(contact_list)
response = super().form_valid(form)
log_campaign_progress(pk=form.instance.pk, status="t2h-created", stage="campaign")
enqueue_handwriting_generation(campaign_pk=form.instance.pk)
return response
Form:
class CampaignCreateForm(forms.ModelForm):
contact_list_file = forms.FileField(required=True)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop(
"user"
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
super().__init__(*args, **kwargs)
class Meta:
model = Campaign
fields = ("name", "message", "contact_list_file")
def clean(self):
# Cammpaign name is unique for the user
try:
Campaign.objects.get(name=self.cleaned_data["name"], user=self.user)
except Campaign.DoesNotExist:
pass
else:
self.add_error(
"name",
ValidationError(
_("You've already created a campaign with this name"), code="BadCampaignName"
),
)
# if an error is attached to a field then the field is removed from cleaned_data
self = check_message_length(self)
self = check_message_text_is_valid(self)
self = check_file_is_valid(self)
self = check_message_tags_exist_in_contact_list(self)
return self.cleaned_data
Model:
# TimeStampedModel inherits form models.Model
class Campaign(TimeStampedModel):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
)
order = models.ForeignKey(
Order,
on_delete=models.SET_NULL,
null=True,
)
name = models.CharField(max_length=80)
notes = models.CharField(max_length=2000, null=False, blank=True)
message = models.TextField(
max_length=1800,
null=False,
blank=False,
)
contact_list = models.JSONField(null=True)
items = models.PositiveIntegerField(null=False, blank=False)
purchased = models.BooleanField(default=False, null=False)
def __str__(self):
return self.name
class Meta:
ordering = ["-modified"]
unique_together = ("user", "name")
... more fields

How can I do username in reviews on django?

I'm doing reviews on django, but I want the user to not be able to enter any name. I want the username in the reviews to match the username of his profile
models.py
class Reviews(models.Model):
name = models.CharField('Имя', max_length=100)
text = models.TextField('Отзыв', max_length=3400)
parent = models.ForeignKey('self', verbose_name='Родитель', on_delete=models.SET_NULL, null=True, blank=True)
book = models.ForeignKey(BookModel, verbose_name='книга', on_delete=models.CASCADE)
name_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
views.py
class MoreInfoView(View):
""" """
def get(self, request, id):
book_info = BookModel.objects.filter(id=id).first()
stuff = get_object_or_404(BookModel, id=self.kwargs['id'])
total_likes = stuff.total_likes()
return render(request, 'bookapp/more_info.html', context={
'id': id,
'book_info': book_info,
'book': BookModel.objects.all(),
'total_likes': total_likes,
})
class AddReview(View):
"""Add Review"""
def post(self, request, pk):
form = ReviewForm(request.POST)
book = BookModel.objects.get(id=pk)
if form.is_valid():
form = form.save(commit=False)
form.book = book
form.save()
return HttpResponseRedirect(reverse('more_info', args=[pk]))
forms
class ReviewForm(forms.ModelForm):
class Meta:
model = Reviews
fields = ("name", "text", 'name_user')
You can add user manually after validating ReviewForm
I also added some changes(suggestions)
models.py
class Reviews(models.Model):
name = models.CharField('Имя', max_length=100)
text = models.TextField('Отзыв', max_length=3400)
parent = models.ForeignKey('self', verbose_name='Родитель', on_delete=models.SET_NULL, null=True, blank=True)
book = models.ForeignKey(BookModel, verbose_name='книга', on_delete=models.CASCADE, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
Setting blank=True makes the field optional.
views.py
class MoreInfoView(View):
""" """
def get(self, request, id):
book_info = BookModel.objects.filter(id=id).first()
stuff = get_object_or_404(BookModel, id=self.kwargs['id'])
total_likes = stuff.total_likes()
return render(request, 'bookapp/more_info.html', context={
'id': id,
'book_info': book_info,
'book': BookModel.objects.all(),
'total_likes': total_likes,
})
class AddReview(View):
"""Add Review"""
def post(self, request, pk):
user = request.user
# User has to be authenticated to create a review. And backend must
# validate it. You should raise PermissionDenied as response or
# redirect user to the login page, or something similar.
if not request.user.is_authenticated:
raise PermissionDenied()
form = ReviewForm(request.POST)
book = BookModel.objects.get(id=pk)
if form.is_valid():
form = form.save(commit=False)
form.book = book
form.user = user
form.save()
return HttpResponseRedirect(reverse('more_info', args=[pk]))
forms.py
class ReviewForm(forms.ModelForm):
class Meta:
model = Reviews
fields = ("name", "text")
I would advise to work with a CreateView [Django-doc] that will simplify a lot of the logic. You can implement this as:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
class AddReviewView(LoginRequiredMixin, CreateView):
form_class = ReviewForm
def get_success_url(self):
return reverse('more_info', args=[self.kwargs['pk']])
def form_valid(self, form):
form.instance.book_id = self.kwargs['pk']
form.name_user = self.request.user
return super().form_valid(form)
In your ReviewForm you thus remove the name_user as fields element.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
Note: normally a Django model is given a singular name, so Review instead of Reviews.
Note: Models normally have no …Model suffix. Therefore it might be better to rename BookModel to Book.

Private messaging system Django

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

Accessing and saving a field that is not included in fields attribute in extending generic class-based CreateView

I'm trying to display the user a form with comments not included. When the user submits the form, then, I want to manually add something to my comments, and then only, saving the object. With default implementation, it doesn't do anything with comments.
! app/views.py
class ContactUsView(SuccessMessageMixin, CreateView):
model = Contact
fields = ['first_name', 'last_name', 'email_address']
success_message = "Thank you for your enquiry. We' ll be in touch shortly."
! app/models.py
class Contact(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True)
email_address = models.EmailField()
comments = models.TextField()
def get_absolute_url(self):
return reverse('contact')
def __str__(self):
return self.first_name
You can do this by overriding the form_valid function of your view.
class ContactUsView(SuccessMessageMixin, CreateView):
model = Contact
fields = ['first_name', 'last_name', 'email_address']
success_message = "Thank you for your enquiry. We' ll be in touch shortly."
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.comments = 'Your comments here'
self.object.save()
return super(ContactUsView, self).form_valid(form)
You can also do this by overriding the save() method of your model Contact
class Contact(models.Model):
# your fields here
def save(self, *args, **kwargs):
# check if it's a new object
if not self.id:
self.comments = 'Your comment'
super(Contact, self).save(*args, **kwargs)

Anonymous user error

I'm trying to save form data to an anonymous user, however I get the below error when trying to save some form data in a CreateView". I'm not clear what the issue is?
ValueError: Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x11126bc18>>": "EUser.user" must be a "User" instance.
Models:
class EUser(models.Model):
online_account = models.BooleanField()
supplier1 = models.OneToOneField(SupplierAccount)
supplier2 = models.OneToOneField(SupplierAccount)
supplier3 = models.OneToOneField(SupplierAccount)
address = models.OneToOneField(Address)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class SupplierAccount(models.Model):
supplier = models.ForeignKey(Supplier)
username = models.CharField(max_length=255)
password = models.CharField(max_length=255)
Form:
class ServiceTypeForm(forms.ModelForm):
# BOOL_CHOICES = ((False, 'No'), (True, 'Yes'))
# online_account = forms.BooleanField(widget=forms.RadioSelect(choices=BOOL_CHOICES))
def __init__(self, *args, **kwargs):
super(ServiceTypeForm, self).__init__(*args, **kwargs)
self.fields['service_type'].initial = 'D'
class Meta:
model = EUser
fields = ('service_type', 'online_account')
View:
class ServiceTypeView(CreateView):
form_class = ServiceTypeForm
template_name = "standard_form.html"
success_url = '/'
def form_valid(self, form):
form.instance.user = self.request.user
super().form_valid(form)
online_account = form.cleaned_data['online_account']
if online_account:
return redirect('../online')
else:
return redirect('../address')
If the user is not logged in, then request.user is an anonymous user. It doesn't make sense to assign an anonymous user to form.instance.user, because an anonymous user does not exist in the database or have a primary key.
How you change your code depends on how you want your application to work.
If you want to allow anonymous users to create service types, then
# if self.request.user.is_authenticated(): # Django < 1.10
if self.request.user.is_authenticated:
form.instance.user = self.request.user
For this to work, you would need to change the user field to make it optional.
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)
After making this change, you'll need to run makemigrations and then migrate, to update the database.
Another option would be to restrict the view to logged in users. In Django 1.9+, You can do this with the LoginRequiredMixin.
from django.contrib.auth.mixins import LoginRequiredMixin
class ServiceTypeView(LoginRequiredMixin, CreateView):
...
I think you can not use the AnonymousUser as value for a ForeignKey to a User.
You should keep is as Null in this case.
class EUser(models.Model):
...
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, default=None)
class ServiceTypeView(CreateView):
...
def form_valid(self, form):
if self.request.user.is_authenticated():
form.instance.user = self.request.user
...

Categories