How to add current user generic detail view? - django

I am learning Django by following an application tutorial written by others called "portfolio". The code is available at http://www.lightbird.net/dbe/. I tried to modify the code so when a user submits a picture, the current user's name can be saved as "creator" defined in model.py as part of the image model. I have a hard time to make this work since I either got the error of unexpected argument of "dpk" or an error related to the number of arguments in "POST" function. Can someone help? Here are the information I have. I highlighted the part of the codes added, which are not part of the original.
models.py:
class Image(BaseModel):
title = CharField(max_length=60, blank=True, null=True)
description = TextField(blank=True, null=True)
image = ImageField(upload_to="images/")
thumbnail1 = ImageField(upload_to="images/", blank=True, null=True)
thumbnail2 = ImageField(upload_to="images/", blank=True, null=True)
width = IntegerField(blank=True, null=True)
height = IntegerField(blank=True, null=True)
hidden = BooleanField(default=True)
group = ForeignKey(Group, related_name="images", blank=True)
created = DateTimeField(auto_now_add=True)
creator = models.ForeignKey(User, null=True, blank=True)
##########################################################
def save(self,**kwargs):
if kwargs.has_key('request') and self.creator is None:
self.creator= kwargs['request'].creator
super(Image, self).save(**kwargs)
forms.py:
class AddImageForm(f.ModelForm):
class Meta:
model = Image
exclude = "width height hidden group thumbnail1 thumbnail2
creator".split()
attrs = dict(cols=70, rows=2)
widgets = dict( description=f.Textarea(attrs=attrs) )
#####################################################################
def save(self, commit=True , *args, **kwargs):
m = super(AddImageForm, self).save(commit=False, *args, **kwargs)
if m.creator is None and kwargs.has_key('request'):
m.creator= kwargs['request'].creator
m.save()
views.py:
class AddImages(DetailView, FormSetView):
"""Add images to a group view."""
detail_model = Group
formset_model = Image
formset_form_class = AddImageForm
template_name = "add_images.html"
extra = 1
############################################################
def post(self, request):
if form.is_valid():
form.save(request=request)
############################################################
def process_form(self, form):
form.instance.update( group=self.get_detail_object() )
def get_success_url(self):
return self.detail_absolute_url()
urls.py:
urlpatterns = patterns("portfolio.views",
(r"^group/(?P<dpk>\d+)/(?P<show>\S+)/" , GroupView.as_view(), {},
"group"),
(r"^group/(?P<dpk>\d+)/" , GroupView.as_view(), {},
"group"),
(r"^add-images/(?P<dpk>\d+)/" , AddImages.as_view(), {},
"add_images"),
(r"^slideshow/(?P<dpk>\d+)/" , SlideshowView.as_view(), {},
"slideshow"),
(r"^image/(?P<mfpk>\d+)/" , ImageView.as_view(), {},
"image"),
(r"^image/" , ImageView.as_view(), {},
"image"),
(r"" , Main.as_view(), {},
"portfolio"),
)

You've overridden the post method in your view to only take the request, but your URLs are passing it the dpk parameter.
You should not be overriding post anyway. The logic you have there should happen in form_valid.
(Also, you should reflect on whether you really need the same logic in three places. I would put all this code in form_valid, and not override save on the form or the model at all.)

Related

send request from signal

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

How to pass pk of detail view into fk of form

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.

Display IntegrityError when trying to validate CreateView based on ModelForm using unique_together

How would I display an integrity error when using class based view's CreateView.
My current Model looks like this :
class Delivery(models.Model):
created_date = models.DateTimeField('date created', editable=False)
modified_date = models.DateTimeField('modified', editable=False)
user_name = models.ForeignKey(User, null=False)
stream_name = models.CharField(max_length=50, null=False)
view_name = models.CharField(max_length=100, null=False, blank=True)
activity_name = models.CharField(max_length=100, null=False, blank=True)
jira = models.URLField()
codereview = models.URLField()
related_streams = models.CharField(max_length=100, choices=sorted(streams()),blank=True)
description = models.TextField(null=False,blank=True)
status = models.BooleanField(default=False, blank=False)
And the corresponding view is :
class CreateEntryView(CreateView):
template_name = 'tracker/entry.html'
model = Delivery
success_url = reverse_lazy('table_view')
status = StreamStatus()
fields = ['stream_name','view_name','activity_name','jira','codereview','related_streams','description','status']
def get_initial(self):
if 'codereview-get' in self.request.GET:
parsedDict = codereviewParser(self.request.GET['codereview-get'])
return {'stream_name':parsedDict['stream'].split('_')[1:2][0],
'view_name':parsedDict['view'],
'activity_name':parsedDict['name'],
'jira':parsedDict['jira'],
'codereview':self.request.GET['codereview-get'],
'description':parsedDict['description'],
'status':parsedDict['status']}
else:
return self.initial.copy()
def form_valid(self, form):
form.instance.user_name = self.request.user
try:
return super(CreateEntryView, self).form_valid(form)
except IntegrityError as e:
messages.error(self.request, "Your data has not been saved!")
return HttpResponseRedirect(self.request.path)
return super(CreateEntryView, self).form_valid(form)
def get_context_data(self, **kwargs):
ctx = super(CreateEntryView, self).get_context_data(**kwargs)
ctx['locked'] = self.status.getLocked()
ctx['unlocked'] = self.status.getUnlocked()
return ctx
I tried a couple of techniques by passing a new context to render_to_response() but then I need to pass the entire context again. Also tried HttpResponse() which I dont like since it directs me to a blank page with a message. I would like to use an alert message to show the error to the user.
What about doing what form_invalid does, but adding your info to the context:
return self.render_to_response(self.get_context_data(form=form, integritymsg='Your data has not been saved!', reason=whatever))
And of course check and show integritymsg/reason in the template.
One thing not immediately obvious is that the arguments passed to get_context_data are added to the context. All the get_context_data being called along the MRO chain add their bit to the context.
ccbv.co.uk is a great tool. Unfortunately you need to inspect the code and find out the flow of execution yourself. It's not complicated, but yes, a diagram would help.
In general, start from as_view, dispatch, get, etc...

Set a form field to the current logged user id

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.

Django CBV Forms prepopulated foreign key dataset

I'm trying to use CBVs as much as possible and want to pre-populate data in a ModelForm based on a generic.CreateView with some data passed in via URL.
I might be over thinking or confusing myself. All code abridged for legibility
We have an inventory system with PartNumbers (abstractions), Carriers (actual instances of PartNumbers with location, serial and quantity numbers) and Movements for recording when items are extracted from the inventory, how much is taken and what Carrier it came from.
I would like to have the "extract inventory" link on the PartNumber detail page, and then have the available carriers ( pn.carrier_set.all() ) auto filled into the FK drop down on the MovementForm.
models.py
class PartNumber(models.Model):
name = models.CharField("Description", max_length=100)
supplier_part_number = models.CharField(max_length=30, unique=True)
slug = models.SlugField(max_length=40, unique=True)
class Carrier(models.Model):
part_numbers = models.ForeignKey(PartNumber)
slug = models.SlugField(max_length=10, unique=True, blank=True, editable=False)
location = models.ForeignKey(Location)
serial_number = models.CharField(max_length=45, unique=True, null=True, blank=True)
qty_at_new = models.IntegerField()
qty_current = models.IntegerField()
class Movement(models.Model):
carrier = models.ForeignKey(Carrier)
date = models.DateField(default=timezone.now())
qty = models.IntegerField()
I have been playing around with get_initial() and get_form_kwargs() without success:
In urls.py I collect the PartNumber via url as pn_slug
url(r'^partnumber/(?P<pn_slug>[-\w]+)/extract/$', views.MovementCreate.as_view(), name='pn_extract'),
forms.py is generic
class MovementForm(forms.ModelForm):
class Meta:
model = Movement
views.py
class MovementCreate(generic.CreateView):
form_class = MovementForm
model = Movement
def get_form_kwargs(self):
kwargs = super(MovementCreate, self).get_form_kwargs()
kwargs['pn_slug'] = self.request.POST.get("pn_slug")
return kwargs
# here we get the appropriate part and carrier and.
# return it in the form
def get_initial(self):
initial = super(MovementCreate, self).get_initial()
# this didn't work, hence using get_form_kwargs
#pn = PartNumber.objects.get(slug=self.request.POST.get("pn_slug"))
pn = PartNumber.objects.get(slug=self[pn_slug])
carriers = pn.carrier_set.all()
initial['carrier'] = carriers
return initial
As it stands, I'm getting "global name 'pn_slug' is not defined" errors - but I doubt that error accurately reflects what I have done wrong.
I have been using these posts as rough guidelines:
How to subclass django's generic CreateView with initial data?
How do I use CreateView with a ModelForm
If I understand you correctly from our comments, all you need is just to change the queryset of the MovementForm's carrier field to set the available options. In that case, I would use get_initial nor get_form_kwargs at all. Instead, I would do it in get_form:
def get_form(self, *args, **kwargs):
form = super(MovementCreate, self).get_form(*args, **kwargs)
pn = PartNumber.objects.get(slug=self.kwargs['pn_slug'])
carriers = pn.carrier_set.all()
form.fields['carrier'].queryset = carriers
return form
Another way to do it would be to use get_form_kwargs:
def get_form_kwargs(self):
kwargs = super(MovementCreate, self).get_form_kwargs()
kwargs['pn_slug'] = self.kwargs.get("pn_slug")
return kwargs
Then, in the form's __init__, set the queryset:
class MovementForm(forms.ModelForm):
class Meta:
model = Movement
def __init__(self, *args, **kwargs):
pn_slug = kwargs.pop('pn_slug')
super(MovementForm, self).__init__(*args, **kwargs)
pn = PartNumber.objects.get(slug=pn_slug)
carriers = pn.carrier_set.all()
self.fields['carrier'].queryset = carriers
Personally, I would prefer the first method as it is less code.