How to assign the User object to save method in Django - python

I am trying to log the activities during save operation to track all the changes to user model. my approach is as follows.
class User(AbstractUser):
undergrad_college = models.CharField(max_length=20, choices=COLLEGE_CHOICES)
undergrad_degree = models.CharField(max_length=20, choices=COLLEGE_DEGREES)
postgrad_college = models.CharField(max_length=20, choices=COLLEGE_CHOICES)
postgrad_degree = models.CharField(max_length=20, choices=COLLEGE_DEGREES)
currently_working_on = models.TextField()
previous_work_experience = models.TextField()
previous_internship_experience = models.TextField()
def __str__(self):
return self.username
def save(self, *args, **kwargs):
Log(user=User, actions="Updated profile",
extra={"undergrad_college": self.undergrad_college,
"undergrad_degree": self.undergrad_degree,
"postgrad_college": self.postgrad_college,
"postgrad_degree": self.postgrad_degree,
"currently_working_on": self.currently_working_on,
"previous_work_experience": self.previous_work_experience,
"previous_internship_experience": self.previous_internship_experience
})
super(User, self).save(args, **kwargs)
my views are like this for handling the logging.
class ActivityMixin(LoginRequiredMixin):
def get_context_data(self, **kwargs):
context = super(ActivityMixin, self).get_context_data(**kwargs)
context['activities'] = Log.objects.filter(user=self.request.user)
return context
class IndexListView(ActivityMixin, ListView):
template_name = 'pages/home.html'
model = User
I get this error while performing the update action.
Cannot assign "<class 'users.models.User'>": "Log.user" must be a "User" instance.
Update view is as follows
class UserUpdateView(LoginRequiredMixin, UpdateView):
form_class = UserForm
# we already imported User in the view code above, remember?
model = User
# send the user back to their own page after a successful update
def get_success_url(self):
return reverse("users:detail",
kwargs={"username": self.request.user.username})
def get_object(self, **kwargs):
# Only get the User record for the user making the request
return User.objects.get(username=self.request.user.username)
How to assign the User model instance to the Log function. I cant get this working. I am Django newbie.

Looks like pretty straightforward, replace User with self:
Log(user=User, ...
Log(user=self, ...

Related

Django auto save the current user in the model on save

I am trying to save the user automatically when saving new data. I mean I want to save the user id for the user who is posting a new thing (submitting a form) without asking that user to choose his account from the droplist (the droplist from the User ForeignKey)
Try 1 :
models.py
class Feedback(models.Model):
.
.
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
editable=False,
)
def save(self, *args, **kwargs):
self.author = get_user_model().objects.get(id=2) #############
return super().save(*args, **kwargs)
It's working if I hard-coded the user-id get(id=2). How can I make the id dynamic?
Edit:
Try 2 :
I tried also doing it with the view:
models.py
class Feedback(models.Model):
.
.
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
editable=False,
null=True
blank=True
)
# without save()
views.py
class FeedbackForm(ModelForm):
class Meta:
model = Feedback
fields = "__all__"
class FeedbackListView(generics.ListCreateAPIView):
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
from_class = FeedbackForm
permission_classes = [FeedbackPermission,] # I also tried AllowAny
def form_valid(self, form, FeedbackForm):
obj = form.save(commit=False) # I also tried self.obj = ...
obj.author = self.request.user # I also tried self.obj.author = ...
obj.save() # I also tried self.obj.save()
return super().form_valid(form)
But on the second try (with forms), the author field remains null.
ListCreateAPIView don't have method form_valid, look at this
In your class FeedbackListView you need to do something like this:
class FeedbackListView(generics.ListCreateAPIView):
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def perform_create(self, serializer):
# The request user is set as author automatically.
serializer.save(author=self.request.user)

Django - Multiple custom models on the same form

I'm using Django 2.1 and PostgreSQL.
My problem is that I'm trying to create a form to edit two different models at the same time. This models are related with a FK, and every example that I see is with the user and profile models, but with that I can't replicate what I really need.
My models simplified to show the related information about them are:
# base model for Campaigns.
class CampaignBase(models.Model):
....
project = models.ForeignKey(Project, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now_add=True)
start_date = models.DateTimeField(null=True, blank=True)
end_date = models.DateTimeField(null=True, blank=True)
....
# define investment campaign made on a project.
class InvestmentCampaign(models.Model):
....
campaign = models.ForeignKey(CampaignBase, on_delete=models.CASCADE, null=True, blank=True)
description = models.CharField(
blank=True,
max_length=25000,
)
....
And the form that I want to create is one that includes the end_date of the FK CampaignBase, and the Description from the InvestmentCampaign.
Now I have this UpdateView to edit the InvestmentCampaign, and I need to adapt to my actual needs, that are also update the CampaignBase model:
class ProjectEditInvestmentCampaignView(LoginRequiredMixin, SuccessMessageMixin, generic.UpdateView):
template_name = 'webplatform/project_edit_investment_campaign.html'
model = InvestmentCampaign
form_class = CreateInvestmentCampaignForm
success_message = 'Investment campaign updated!'
def get_success_url(self):
return reverse_lazy('project-update-investment-campaign', args=(self.kwargs['project'], self.kwargs['pk']))
# Make the view only available for the users with current fields
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
# here you can make your custom validation for any particular user
if request.user != self.object.campaign.project.user:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
# Set field as current user
def form_valid(self, form):
campaign = InvestmentCampaign.objects.get(pk=self.kwargs['campaign'])
form.instance.campaign = campaign
form.instance.history_change_reason = 'Investment campaign updated'
return super(ProjectEditInvestmentCampaignView, self).form_valid(form)
def get_context_data(self, **kwargs):
project = Project.objects.get(pk=self.kwargs['project'])
context = super(ProjectEditInvestmentCampaignView, self).get_context_data(**kwargs)
context['project'] = project
return context
My forms are:
class CreateCampaignBaseForm(forms.ModelForm):
class Meta:
model = CampaignBase
fields = ('end_date',)
widgets = {
'end_date': DateTimePickerInput(),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateCampaignBaseForm, self).__init__(*args, **kwargs)
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
class CreateInvestmentCampaignForm(forms.ModelForm):
class Meta:
model = InvestmentCampaign
fields = ('description')
widgets = {
'description': SummernoteWidget(attrs={'summernote': {
'placeholder': 'Add some details of the Investment Campaign here...'}}),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateInvestmentCampaignForm, self).__init__(*args, **kwargs)
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
I've read everywhere that the best way of doing this is using function based views, and call each of the forms that I have and then do the validation. the thing is that I don't know how can I populate the fields with the right object in both forms, and also, I don't know how to do the equivalent of the get_context_data nor getting the self arguments to do the equivalent of the get_success_url (because with function based views I only have the request attr so I can't access the kwargs).
I've seen some people using the django-betterforms, but again, the only examples are with the auth and profile models and I don't see the way to replicate that with my own models.
Thank you very much.
If the only thing you want to change is one field end_date on BaseCampaign, then you should use just one form. Just add end_date as an additional field (e.g. forms.DateTimeField()) on your CreateInvestmentCampaignForm and in your form.valid() method, after saving the form, set the value on the associated campaign:
def form_valid(self, form):
inv_campaign = form.save(commit=False)
inv_campaign.campaign.end_date = form.cleaned_data['end_date']
inv_campaign.campaign.save()
inv_campaign.history_change_reason = ...
return super().form_valid(form)
Here's how to add end_date to your form and initialize it correctly:
class CreateInvestmentCampaignForm(ModelForm):
end_date = forms.DateTimeField(blank=True)
class Meta:
model = InvestmentCampaign
fields = ('description')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.campaign:
self.fields['end_date'].initial = self.instance.campaign.end_date
Based on the conversation on the answer of #dirkgroten, I've developed what worked for me and what I'm actually using, but I market his answer as correct because his code is also functional.
So, meanwhile he is initiating the values on the form, I'm using the view to do that by adding a def get_initial(self): and also adding the validation on the def form_valid(self, form)::
On the view:
...
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(ProjectEditInvestmentCampaignView, self).get_initial()
initial['end_date'] = self.object.campaign.end_date
return initial
...
# Set field as current user
def form_valid(self, form):
form.instance.history_change_reason = 'Investment campaign updated'
is_valid = super(ProjectEditInvestmentCampaignView, self).form_valid(form)
if is_valid:
# the base campaign fields
campaign = form.instance.campaign
campaign.end_date = form.cleaned_data.get("end_date")
campaign.save()
return is_valid
And on the form I just added the end_date field:
class CreateInvestmentCampaignForm(forms.ModelForm):
end_date = forms.DateTimeField()
class Meta:
model = InvestmentCampaign
fields = ('description',)
widgets = {
'description': SummernoteWidget(attrs={'summernote': {
'placeholder': 'Add some details of the Investment Campaign here...'}}),
'end_date': DateTimePickerInput(), # format='%d/%m/%Y %H:%M')
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateInvestmentCampaignForm, self).__init__(*args, **kwargs)
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True

Django Inline form validation ignored partly filled form

I have got the code from https://github.com/adandan01/mybook, the code is working fine, even when I have updated it to Django 2. It's very simple project for adding a person in a form, and his/her relatives in the inline form. Everything works but when I add a relative name and forget to add his relationship, and submitted the form, unfortunately, that record will not pass the validation but will give no error messages as well. Django will ignore the entire record. For example, the record for Hawra in the image, will not be saved and Django will remove it. For this simple App there are only two fields to be filled (name and relationship), but I'm working on app with 8 fields, and it will be difficult to lose the data. is there any way to make django do the validation in the formset/subform as long as any fields have data and will ask the user to fill all required fields?
models.py:
class Profile(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
created_date = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('profile-update', kwargs={'pk': self.pk})
def __unicode__(self):
return "%s %s" % (self.first_name, self.last_name)
class FamilyMember(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.PROTECT)
name = models.CharField(max_length=100)
relationship = models.CharField(max_length=100)
form.py
class ProfileForm(ModelForm):
class Meta:
model = Profile
exclude = ()
class FamilyMemberForm(ModelForm):
class Meta:
model = FamilyMember
exclude = ()
FamilyMemberFormSet = inlineformset_factory(Profile, FamilyMember,
form=FamilyMemberForm, extra=1)
views.py
class ProfileCreate(CreateView):
model = Profile
fields = ['first_name', 'last_name']
class ProfileFamilyMemberCreate(CreateView):
model = Profile
fields = ['first_name', 'last_name']
success_url = reverse_lazy('profile-list')
def get_context_data(self, **kwargs):
data = super(ProfileFamilyMemberCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['familymembers'] = FamilyMemberFormSet(self.request.POST)
else:
data['familymembers'] = FamilyMemberFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
familymembers = context['familymembers']
with transaction.atomic():
self.object = form.save()
if familymembers.is_valid():
familymembers.instance = self.object
familymembers.save()
return super(ProfileFamilyMemberCreate, self).form_valid(form)
I found the solution here django inline_formset - form.empty_permitted = False doesn't work
I had to add the following code before if (familymembers.is_valid():...) in the create and update class, so, now Django will show the error if I entered data in the Name field only and will tell me the Relationship field is required.
if familymembers.is_valid() == False:
return self.render_to_response(self.get_context_data(form=form,familymembers=familymembers ))

I want to write a logic in views.py and save data in serializer

I am making a system saved user data to model.I want to write a part of logic in views.py and a part of save data in serializer.I want to make a system password is changed into hash.Now I wrote codes in views.py,
class InfoViews(viewsets.ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def create(self,request, *args, **kwargs):
user = Info()
passwd = request.data['password']
md5 = hashlib.md5()
md5.update(passwd.encode('utf-8'))
user.password = md5.hexdigest()
user.save()
return JsonResponse({"data":"data"})
in serializer.py
class InfoSerializer(serializers.ModelSerializer):
created_time = serializers.DateTimeField(required=False)
class Meta:
model = Info
fields = '__all__'
def create(self, validated_data):
user = Info(
email=validated_data['email'],
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
in models.py
class Info(models.Model):
username = custom_fields.NotEmptyCharField(max_length=100, unique=True)
email = models.EmailField()
password = custom_fields.NotEmptyCharField(max_length=100)
class Meta:
db_table = 'info'
def __str__(self):
return '%s: %s' % (self.username, self.email)
Now whenI tried to save user data to model,django.core.exceptions.ValidationError: ['Can not be empty!'] error happens.What is wrong in my codes?I searched http://www.django-rest-framework.org/api-guide/serializers/ .How should I fix this?
You are not using InfoSerializer() serializer so, remove create() method from that, and change your views.py as below,
class InfoViews(ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def create(self, request, *args, **kwargs):
serializer = InfoSerializer(request.data).data
serializer.pop('created_time', None)
passwd = serializer['password']
md5 = hashlib.md5()
md5.update(passwd.encode('utf-8'))
serializer['password'] = md5.hexdigest()
Info.objects.create(**serializer)
return JsonResponse({"data": "data"})
My Friendly Suggestion
I don't think this is a good method to acheive so, So changes below will do better (I think so ;))
views.py
class InfoViews(ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
serializer.py
import hashlib
class InfoSerializer(serializers.ModelSerializer):
created_time = serializers.DateTimeField(required=False)
def set_password(self, raw_pwd):
md5 = hashlib.md5()
md5.update(raw_pwd.encode('utf-8'))
return md5.hexdigest()
class Meta:
model = Info
fields = '__all__'
def create(self, validated_data):
validated_data['password'] = self.set_password(validated_data['password'])
return super().create(validated_data)
Update
Alternative create() for serializer,
def create(self, validated_data):
validated_data['password'] = self.set_password(validated_data['password'])
user = Info.objects.create(
email=validated_data['email'],
username=validated_data['username'],
password=validated_data['password']
)
# you can avoid large number of assignment statements (as above) by simply calling "super()" method
return user
You're getting a validation error because email is a required field. When you run user.save(), the email value isn't sent, hence the ValidationError.
You should definitely be saving everything in your view, the Serialiser is just a way to change the way the data is presented by DRF.
Also, you really shouldn't be using md5 to save your passwords. Just use the built in Django method: user.set_password(password) - Django will take care of the hashing for you and much more securely.

Django forms ChoiceField not selecting the wanted value

I created a django form (IssueForm) which is meant to be used to register an object which is instance of one of my models (Issue). Following are the model:
model.py
class Issue(models.Model):
TYPE_FIELDS = [
("Math", "Math"),
("Physics", "Physics"),
("Programming", "Programming"),
("Arts", "Arts")
]
issue_text = models.TextField(default="Please insert text")
issue_description = models.TextField(default="Newly created")
issue_deadline = models.DateField()
issue_field = models.CharField(max_length=30, choices=TYPE_FIELDS)
published_by = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.issue_description
the form used:
forms.py
class IssueForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(IssueForm, self).__init__(*args, **kwargs)
TYPE_FIELDS = [
("Math", "Math"),
("Physics", "Physics"),
("Programming", "Programming"),
("Arts", "Arts")
]
issue_text = forms.CharField(widget=forms.Textarea, required=True)
issue_description = forms.CharField(widget=forms.Textarea, required=True)
issue_deadline = forms.DateField(required=True)
issue_fields = forms.ChoiceField(choices=TYPE_FIELDS, required=True)
class Meta:
model = Issue
fields = [
'issue_text',
'issue_description',
'issue_deadline',
'issue_fields'
]
def save(self, commit=True):
issue = super(IssueForm, self).save(commit=False)
issue.issue_text = self.cleaned_data['issue_text']
issue.issue_description = self.cleaned_data['issue_description']
issue.issue_deadline = self.cleaned_data['issue_deadline']
issue.issue_fields = self.cleaned_data['issue_fields']
if commit:
issue.published_by = self.user
issue.save()
return issue
and the related view:
views.py
def create_issue(request):
if ExtendedUser.objects.filter(user=request.user).exists():
if request.method == 'POST':
form = IssueForm(request.user, request.POST)
if form.is_valid():
form.save()
return redirect("/issues")
else:
form = IssueForm(request.user)
args = {'form': form}
return render(request, "issues/create_issue.html", args)
else:
raise Http404("You are not allowed to perform this action")
The forms works for every field in the model, they are all registered right, except for issue_fields. If i try giving a default value to the field in the model, that is the value that is saved on the database, otherwise I just get an empty field. Also I believe the problem comes from the form used, because if i try to create a new issue from the django admin interface it works just fine.
I feel like it's one of those silly mistakes, but I'm just starting with django and python in general and cannot figure it out on my own.
Thank you for your time!!
The field on your model is called issue_field, but you set issue_fields.
Note that also you are doing far more work here than necessary. Your save method completely duplicates what the superclass does already; you should remove all that code except for the setting of the user value.
enter code hereIf you want to use Choices, you haven't to write one more time list of choices in your forms.py file.
This is an example :
#In your models.py file
LIST_CHOICE = (('A','A'), ('B','B'))
class Test(models.Model) :
foo = models.CharField(choices=LIST_CHOICE, verbose_name="foo")
and
#In your form.py file
TestForm(forms.Modelform) :
class Meta :
model = Test
fields = ['foo']
It's not necessary to overwrite LIST_CHOICE in your form file ;)
So, dont touch to your model.py file, but in your form.py file, just write :
class IssueForm(forms.ModelForm):
issue_text = forms.CharField(widget=forms.Textarea)
issue_description = forms.CharField(widget=forms.Textarea)
def __init__(self, user, *args, **kwargs):
self.user = user
super(IssueForm, self).__init__(*args, **kwargs)
class Meta:
model = Issue
fields = [
'issue_text',
'issue_description',
'issue_deadline',
'issue_fields'
]
Don't forget to remove s in issue_field ;)

Categories

Resources