Okay, so first of all the situation is not quite easy as in the title. So I want to create form which can create object based on Cycle model and update only one field in Post model (field in question is 'cycle_title'). What is more its isn't only one post where this post have to be updated but there are several of it (all post's titles are saved in Cycle.post).
views
class CycleCreateView(LoginRequiredMixin, BSModalCreateView):
template_name = 'blog/cycle_form.html'
form_class = CycleForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(user=self.request.user)
return kwargs
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
reverse_user = self.request.user
return reverse('profile', kwargs={'username': reverse_user})
forms
class CycleForm(BSModalForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
if user is not None:
self.fields['posts'].queryset = Post.objects.filter(author=user)
class Meta:
model = Cycle
fields = ['title', 'description', 'posts']
widgets = {
'posts': forms.CheckboxSelectMultiple(),
}
models
class Post(models.Model):
title = models.CharField(max_length=100, unique=True)
content = MDTextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
cycle_title = models.CharField(max_length=100, default='')
class Cycle(models.Model):
title = models.CharField(max_length=100, unique=True)
description = models.TextField(max_length=500, default="Brak opisu")
date_created = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
posts = models.ManyToManyField(Post)
I was thinking about a solution like this:
for i in form.cleaned_data['posts']:
post = Post.objects.get(title=form.cleaned_data['title'][i])
post.cycle_title = form.cleaned_data['title']
post.save()
But I doubt if it is good way to resolve this issue
A package has been built just to handle this exact scenario, django-shapeshifter. You can find it here:
https://github.com/kennethlove/django-shapeshifter
The basic premise is to create two model forms, then include them in the same template. The example given shows how to update a User and a Profile model from the same view and form. It sounds like that is a match for your problem. Full disclosure, I'm a contributor to this package, but is was created exactly because of frustrations like your own!
Related
I have an exam model that whenever an instance is created, instances of the Question model to the number that is specified in the Exam are created(using post_save signal). Also, I have a Go code that whenever a request is sent, fills out 3 fields of the Question model. My problem is how can I send this request in the signal part.
The codes are as followed:
models.py:
class Exam(models.Model):
title = models.CharField(max_length=255)
subject = models.CharField(max_length=255, default='')
organizer = models.CharField(max_length=255, default='...')
description = models.TextField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
duration = models.DurationField()
number_of_questions = models.PositiveSmallIntegerField()
order = models.IntegerField(default=0)
def __str__(self):
return self.title
class ExamQuestion(models.Model):
exam = models.ForeignKey('ExamApply', on_delete=models.CASCADE)
question_template = models.ForeignKey(QuestionTemplate, on_delete=models.CASCADE)
text = models.TextField(max_length=5000, null=True, blank=True)
question_params = models.JSONField(null=True, blank=True)
answer_choices = models.JSONField(null=True, blank=True)
answer_given = models.JSONField(default=dict, null=True, blank=True)
correct_answer = models.JSONField(null=True, blank=True)
data = models.JSONField(null=True, blank=True)
is_correct = models.BooleanField(null=True)
order = models.IntegerField(null=True, blank=True)
def __str__(self):
return str(self.id)
class ExamApply(models.Model):
class Status(models.TextChoices):
CREATED = 'CR', 'Created'
STARTED = 'ST', 'Started'
FINISHED = 'FN', 'Finished'
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
status = models.CharField(max_length=2, choices=Status.choices, default=Status.CREATED)
def get_score(self):
score = ExamQuestion.objects.filter(exam=self, answer_given=F('correct_answer')).count()
return score
signals.py:
#receiver(post_save, sender=ExamApply)
def create_examapply_examquestion(sender, instance, created, **kwargs):
if created:
for _ in range(instance.exam.number_of_questions):
ExamQuestion.objects.create(exam=instance)
id = ExamQuestion.objects.all().last().id
return redirect('/question/' + str(id) + '/') #doesnt work
#receiver(post_save, sender=ExamApply)
def save_examapply_examquestion(sender, instance, created, **kwargs):
instance.exam.save()
urls.py related to the part I want:
urlpatterns = [
path('questions/<int:pk>/', UpdateQuestionAPI.as_view()),
]
views.py:
class UpdateQuestionAPI(generics.RetrieveUpdateDestroyAPIView):
queryset = ExamQuestion.objects.all()
serializer_class = IntegrateQuestionSerializer
lookup_field = 'pk'
def get(self, request, *args, **kwargs):
question = ExamQuestion.objects.filter(pk=kwargs['pk'])
serializer = ExamQuestionSerializer(question, many=True)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({"message": "updated successfully"})
else:
return Response({"message": "failed", "details": serializer.errors})
serializers.py:
class IntegrateQuestionSerializer(serializers.ModelSerializer):
class Meta:
model = ExamQuestion
fields = ['question_params', 'answer_choices', 'correct_answer',]
class ExamQuestionSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
class Meta:
model = ExamQuestion
fields = ['title']
def get_title(self, obj):
return obj.exam.exam.title
I had the idea of using redirect(to the update view), but it doesn't work.
First of all, it makes no sense to use request in the signal part.
This code is related to the model and the orm and is not responsible of the request part. That's the view work.
The signal you are using is related to the creation of the ExamApply model. The view you posted doesn't create a ExamApply so it will not get called at all.
Just so you know, usually signals are avoided because they lead to complicated code. To quote the documentation “Signals give the appearance of loose coupling, but they can quickly lead to code that is hard to understand, adjust and debug. Where possible you should opt for directly calling the handling code, rather than dispatching via a signal.”
Here you have multiple issues in your signals, you have a return in a loop, that will stop the loop after the first iteration. Then you return an HttpResponse (in the form of redirect) but you are not in a view so it doesn't make sense.
It's not clear what you want to do because the view and the serializer you posted are not related to the creation of ExamApply as I understand it what you could do (one of):
Override the save method of ExamApply to create the questions.
Or handle the creation of questions in the view that create the ExamApply
For example:
def save(self, *args, **kwargs):
if not self.pk:
# not self.pk means a creation.
for _ in range(self.exam.number_of_questions):
question = ExamQuestion.objects.create(exam=self.exam)
# You might want to link question to user / exam apply here?
self.exam.question_set.add(question)
super().save(*args, **kwargs)
In your ExamApply creation view or serializer you might want to return the questions id, if you use DRF it's trivial to do so: https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
But you might want to link questions to ExamApply and/or user and not just Exam.
On a side note, for the future, if you need the created object id you should use
question = ExamQuestion.objects.create(exam=instance)
question.id
I want to filter Blog Post objects or records based on the Post Category and a User that uploaded the Post record, it gives me an error when I try to do filter, this is the error.
ValueError at /dashboard/filter-post/
The QuerySet value for an exact lookup must be limited to one result using slicing.
Here is my models.py
class Category(models.Model):
cat_name = models.CharField(max_length=100, verbose_name='Category Name')
cat_desc = models.TextField(blank=True, null=True)
def __str__(self):
return self.cat_name
class Meta():
verbose_name_plural='Category'
class Post(models.Model):
pst_title = models.CharField(max_length=150)
pst_image = models.ImageField(blank=True, null=True, upload_to='uploads/')
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.pst_title
#property
def img_url(self):
if self.pst_image:
return self.pst_image.url
on forms.py
class FilterForm(forms.ModelForm):
user = forms.ModelChoiceField(
queryset=User.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}))
category = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
widget=forms.SelectMultiple(attrs={'class': 'form-control js-example-disabled-results'}))
catch_bot = forms.CharField(required=False,
widget=forms.HiddenInput, validators=[validators.MaxLengthValidator(0)])
class Meta():
fields = ['user', 'category' ]
model = Post
on views.py
def filter_post(request):
post = FilterForm(request.GET)
queryset = Post.objects.all()
if post.is_valid():
user=post.cleaned_data.get('user')
category=post.cleaned_data.get('category')
if user and category:
queryset = queryset.filter(user__username=user, category__cat_name=category)
return render(request, 'backend/filter-post.html', {'query':queryset, 'post':post})
I am having challenges properly filtering this in my views any help?
Try this:
instead of this:
queryset = queryset.filter(user__username=user, category__cat_name=category)
use this:
queryset = queryset.filter(user=user, category=category)
Also don't name your model fields after the model name, just use name instead of pst_name or cat_name, you will see that when you will try access these values there will be no confusion.
UPDATE
Ok, maybe try to rewrite your view like this:
def filter_post(request):
posts = Post.objects.all()
form = FilterForm(request.GET) # its best practice to call your form instance `form` in the view so that the next line has better readability
if form.is_valid():
user=post.cleaned_data['user']
category=post.cleaned_data['category']
if user:
posts = posts.filter(user=user)
if category:
posts = posts.filter(category=category)
return render(request, 'backend/filter-post.html', {'posts':posts})
Good afternoon, I am fairly new to Django and I am not sure how to go about this.I have a Django 2.2 project with these models:
class Equipment(models.Model):
name = models.CharField(
max_length=15,
unique=True,
verbose_name='asset name')
asset_cat = models.ForeignKey('category',on_delete=models.PROTECT,verbose_name='asset category')
asset_loc = models.ForeignKey('location',on_delete=models.PROTECT,verbose_name='asset location')
state = models.ForeignKey('status',on_delete=models.PROTECT,verbose_name='status')
brand = models.CharField(
max_length=15,
unique=False,
blank=True)
model = models.CharField(
max_length=12,
unique=False,
blank=True,
verbose_name='model number')
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
return reverse('equipment-detail', args=[str(self.id)])
class Meta:
ordering = ['asset_cat', 'name']
verbose_name_plural = 'pieces of equipment'
class Action(models.Model):
name = models.ForeignKey('equipment',on_delete=models.PROTECT,verbose_name='asset name',blank=False)
dt = models.DateTimeField(
auto_now_add=True,
verbose_name='date and time of incident')
incident = models.TextField(
blank=True,
null=True)
CHANGE = 'CHANGE'
SERVICE = 'SERVICE'
ACTION_CHOICES = (
(CHANGE, 'CHANGE'),
(SERVICE, 'SERVICE')
)
act = models.TextField(
blank=True,
choices=ACTION_CHOICES,
null=True,
verbose_name='action taken')
act_detail = models.TextField(
verbose_name='action detail',
blank=False)
result = models.TextField(
blank=True,
null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('service-detail', args=[str(self.id)])
class Meta:
ordering = ['-dt']
verbose_name_plural = 'service calls'
I have an Equipment Detail View like this:
class EquipmentDetailView(generic.DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get_context_data(self, **kwargs):
context = super(EquipmentDetailView, self).get_context_data(**kwargs)
return context
The detail view has two buttons: edit and service. If I click edit I have a model form that allows me to edit that instance of the Equipment model successfully.
However, when I click the service button, my form comes up to create an instance of the Action model, but when I submit it tells me that the null value in name_id violates the not null constraint.
It looks like my question is, how can I pass equipment.id from the Equipment Detail view to action.name of the action create form and keep the service button concept?
Action Form:
class ServiceForm(forms.ModelForm):
class Meta:
model = Action
fields = ['incident', 'act_detail', 'result']
Action (actually service) view:
class EquipmentServiceView(generic.CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
Assuming you don't want to go with simpliest solution to include name in form fields and have urls setup as:
/equipment/<id> - equipment detail view
/service - service (or action) create view
There are several ways of passing equipment id:
1) From url
We are going to change url to accept equipment_id. That means instead of /service you will have url /equipment/<equipment_id>/service.
Probably best solution - you will use URL according to REST architecture and will have very clear structure. Client can access page from anywhere (like just copy paste link from mail) and it will work.
urls.py:
urlpatterns = [
path('equipment/<int:pk>', EquipmentDetailView.as_view(), name='equipment-detail'),
path('equipment/<int:equipment_pk>/service', EquipmentServiceView.as_view(), name='service-create')
]
Your service button should look like this: service
and finally your view:
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.kwargs['equipment_pk']
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
2) Session data
In case you want to preserve service url without adding equipment_id, you can store equipment id either in session data(on your server) or in cookies(on client). That's not exactly good - client have to go to EquipmentDetailView prior to creating Service, but this will keep your urls intact.
views.py:
class EquipmentDetailView(DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
request.session['last_equipment_pk'] = self.object.pk
return response
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.request.session.get('last_equipment_pk')
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
P.S.: name is bad field name for ForeignField - should be something like equipment or so. Those labels usually associate with CharField and expected to be strings.
Here is the code
models.py
class Submission(models.Model):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
CAR_TYPES = (
(CAR, 'Car'),
(TRUCK, 'Truck'),
(VAN, 'Van'),
(SUV, 'SUV'),
)
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=MEDIA_TYPES, default=CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submission')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
views.py
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
def get_queryset(self):
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
return context
urls.py
url(r'^(?P<slug>[\w-]+)/$', SubmissionCategoryList.as_view(), name='submit_category'),
The code works fine. When I go to localhost:8000/car/ It shows the list view for only the CARS submission_type, etc. But, when I type in a url that isn't a part of the choices in CAR_TYPES, for example, localhost:8000/boat/, django still shows the template for this view. My question is: How do I limit the number of choices the slug should accept? And, if it is not a part of the CAR_TYPES choices, how do I get it to ignore this view?
Have you tried validating your "slug" in views?
views.py
def get_queryset(self):
if self.kwargs['slug'] is not None and self.kways['slug'].lower() in project.settings.CAR_TYPE:
# return your queryset.filter ...
else:
# return other template or redirect to other views.
project.settings.py
CAR_TYPE = ['car', 'truck', 'van', 'suv']
or, simply just edit url regex:
(?P<slug>car|truck|van|suv)
not the best approach but definitely solve your problem.
Ok so thanks to Anzel I figured out a great solution to this problem. I'm putting this here for anyone who needs this.
I created a reference.py file that looks like this. You can call it whatever you like, I just call it reference because it's what it does.
from enum import Enum
class CarTypes(Enum):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
I changed my models.py to import reference.py
I changed choices in submission_type to choices=tuple([(auto.name, auto.value) for auto in CarTypes])
from .reference import CarTypes
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=tuple([(auto.name, auto.value) for auto in CarTypes]), default=CarTypes.CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
And here is the final working views.py for categories
from .reference import CarTypes
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
CAR_TYPES = [auto.value for auto in CarTypes]
def get_queryset(self):
if self.kwargs['slug'] in self.CAR_TYPES:
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
else:
return Http404
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
context['submission_type'] = self.kwargs['slug']
return context
You can put this same queryset in a DetailView and it should work when a user searches site.com/car/2 or something like that
I have a class Task with the following implementation:
class Task(models.Model):
author = models.ForeignKey(Author, unique=False)
name = models.CharField(max_length=255)
completed = models.BooleanField(default=False)
deadline = models.DateTimeField(null=True, blank=True)
pub_date = models.DateTimeField(auto_now_add=True, editable=False)
edit_date = models.DateTimeField(auto_now_add=True, auto_now=True, editable=False)
tag = models.ManyToManyField(Tag, related_name='tags', null=True, blank=True, default=None)
# group = models.ForeignKey(Group, blank=True, default=None)
def __str__(self):
return u'%s' % (self.name)
def toggle_complete(self):
self.completed = not self.completed
def is_past_deadline(self):
return timezone.now() > self.deadline
And I am trying to do a simple form that creates a new Task with a Title. But, as you can see, the author attribute can not be null (and don't want to, of course).
Author is implemented as follows:
class Author(models.Model):
user = models.OneToOneField(User, primary_key=True)
name = models.CharField(max_length=30)
def __str__(self):
return u'%s' % (self.user)
I tried and tried to hide the author field and, overriding methods like get_form_kwargs, form_valid, get_form to set it to the current logged user, but I always fail. Simply, the id is neither sent as post data (as seein in the debug trace), nor fetched from the view itself.
My best result has been showing the author field, creating the user correctly, but getting a "success_url" not found, even with the model having a get_absolute_url method declared.
The view I am working with is implemented like:
class HomeView(CreateView, MultipleObjectMixin):
# common
model = models.Task
template_name = 'home.html'
#form
form_class = TaskForm
# list
object_list = model.objects.all()
context_object_name = 'tasks'
paginate_by = 40
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('taskr:index'))
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(HomeView, self).get_form_kwargs()
kwargs['initial']['author_id'] = self.request.user.id
return kwargs
def form_valid(self, form):
task = form.save(commit=False)
task.user = models.Author.objects.get(user=self.request.user) # use your own profile here
task.save()
return HttpResponseRedirect(self.get_success_url())
For the record, the MultipleObjectMixing part of the view works flawlessly.
I am desperate, is there any good resource for Django forms, one like http://ccbv.co.uk/? Thanks.
After a good night sleep, while cleaning up, I tried fixing the form_valid in the CreateView descendant and I got it right.
The trick is in
task.user = models.Author.objects.get(user=self.request.user)
and it failed to me because of desperate copy-pasting. The problem was that my Task model has no user attribute, but an author. So
task.author = models.Author.objects.get(user=self.request.user)
fixes it all.
Sorry for the stupid question.