Create object and display it in same page with htmx - django

The function I am trying to implement is that, when the form is submitted in the create view, I would like to display the created object beneath it without the page refreshing. I know the way to implement this is through HTMX. The object that is being created is a key which is unique and gets randomly generated. However it does not work properly. Right now, first time clicking the button it creates a key and displays it but the second time it tries to show the previous key and because it already exists it is not unique and therefore it shows nothing.
This is the model
class Key(models.Model):
key = models.CharField(max_length=10, null=False, default=get_random_string(length=10), unique=True)
amount = models.IntegerField(default=1)
created = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
return reverse("key", kwargs={"id": self.id })
I have two views, a create view and a detail view, I would have liked to used CBV but I can't get my head around how I would implement that, so I am sticking to function based views. Right now the views are looking like this
#login_required
def key_create_view(request):
form = CreateKeyForm(request.POST or None)
context = {
"form": form,
}
if form.is_valid():
obj = form.save(commit=False)
obj.save()
key_url = reverse('key', kwargs={'id':obj.id})
if request.htmx:
context = {
"object": obj,
"key_url": key_url
}
return render(request, "base/components/key.html", context)
#return redirect(obj.get_absolute_url())
return render(request, "base/form.html", context)
#login_required
def key_detail_hx_view(request, id=None):
if not request.htmx:
raise Http404
try:
obj = Key.objects.get(id=id)
except:
obj = None
if obj is None:
return HttpResponse("Something went wrong")
context = {
"object": obj
}
return render(request, "base/components/key.html", context)
In the template have I built it like this
<form action="" method="POST">
{% csrf_token %}
<div id="key" class="text-center">
<button hx-post="{{key_url}}" hx-target="#key" hx-swap="beforeend" type="submit">Create new key</button>
</div>
</form>
I would like it to generate a new key each time the button is pressed and replace the previous one. Does anybody know how I can implement that?
EDIT
These are my urls
path('createkey', key_create_view, name='create_key'),
path('createkey/<int:id>', key_detail_hx_view, name='key')
This is the form
class CreateKeyForm(forms.ModelForm):
class Meta:
model = Key
fields = ()
So it is an empty form, just to create the object.

The key creating form's hx-post endpoint should be the key_create_view not the key displaying view. Actually, on the form page {{ key_url }} is empty. Furthermore, since you want to show only the last generated key, the swapping method should be the default innerHTML, so HTMX will replace the content inside the #key div with the new one.
<form action="" method="POST">
{% csrf_token %}
<div class="text-center">
<button hx-post="{% url 'create_key' %}" hx-target="#key" type="submit">Create new key</button>
</div>
</form>
<!-- HTMX will swap the returned key here -->
<div id="key"></div>
Edit:
The duplicate key error is caused by the wrong approach to add a dynamic default value. If you actually call the function (get_random_string(10)) it will be executed once and each key will receive the same random string. You need to pass the function as a reference, so each time Django creates a new model, it will call this function, creating a new random string. Since get_random_string needs a parameter, we need to create a small wrapper function.
def get_default_random_string():
return get_random_string(length=10)
class Key(models.Model):
key = models.CharField(max_length=10, null=False, default=get_default_random_string, unique=True)
amount = models.IntegerField(default=1)
created = models.DateTimeField(auto_now_add=True)

Related

How to create delete button for deleting a record in django

I am trying to delete a record
My views.py file is
def AnnouncementDelete(request, pk):
announcement = get_object_or_404(Announcement, pk=pk)
if request.method=='POST':
announcement.delete()
return redirect('/')
return render(request, 'classroom/announcement_confirm_delete.html')
and my html file is
{% extends "classroom/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<form action="{% url 'classroom:AnnouncementDelete' announcement.id %}" method="post">
{% csrf_token %}
<input type="submit" value="Delete cat">
</form>
{% endblock content%}
my url.py file has the pattern
url(r'^delete/(?P<pk>[0-9]+)/$', views.AnnouncementDelete, name='AnnouncementDelete'),
and the model from where I want to delete the record is
class Announcement(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
def __str__(self):
return self.title
when I am trying to access http://127.0.0.1:8000/classroom/delete/1/
it is giving me the following error
NoReverseMatch at /classroom/delete/1/
Reverse for 'AnnouncementDelete' with arguments '('',)' not found. 1 pattern(s) tried: ['classroom/delete/(?P<pk>[0-9]+)/$']
Also i am a beginner to django and not very familiar with url(r'^delete/(?P<pk>[0-9]+)/$', views.AnnouncementDelete, name='AnnouncementDelete'), way. I generally use the ```path`` way.
EDIT
This is the view for uploading assignment
#login_required
def upload_announcement(request):
if(request.user.is_teacher==False):
return HttpResponse("This forms requires teacher previlodge")
else:
assignment_uploaded = False
teacher = request.user.Teacher
if request.method== 'POST':
form = AnnouncementForm(request.POST)
if form.is_valid():
upload = form.save(commit=False)
upload.teacher = teacher
upload.save()
assignment_uploaded = True
else:
form = AnnouncementForm()
return render(request, 'classroom/announcement_form.html', {'form':form, 'assignment_uploaded':assignment_uploaded})
This is the view for displaying all assignments
class AnnouncementListView(ListView):
context = {
'announcements' : Announcement.objects.all()
}
model = Announcement
template_name = 'classroom/all_announcements.html' #<app>/<model>_<viewtype>.html
context_object_name = 'announcements'
You can discard the url() line and use the path() variant for this like that:
path("delete/<int:pk>/", view=views.AnnouncementDelete, name="AnnouncementDelete"),
If that doesn't solve your problem, please report back with the view that renders the delete page.
In your html template you are using a variable (announcement) that you never pass from your view.
If i am thinking of this right, you should change your view to:
def AnnouncementDelete(request, pk):
announcement = get_object_or_404(Announcement, pk=pk)
if request.method=='POST':
announcement.delete()
return redirect('/')
return render(request, 'classroom/announcement_confirm_delete.html', {'announcement': announcement})
# this last guy over here sends the desired object to the template for rendering.
You did't ask for this but...
Since you mention in your question that you use path most of the times, i assume that you just wanted to test url.
Regarding the proper use of url variant check out this answer.

Passing data to form (from POST and url)

I'm not sure how to pass data from a given object (profile) and from form (request.POST) to one model (many to many relation).
I have two models: Profile and Keyword. Users can define many profiles (for different subjects) and in each profile they can define many keywords (used later by a crawler). It is possible that the same keyword is in many profiles, and one profile can have many keywords.
Now, I have a view adding new profile to user, and in next step I want to add view adding keyword/keywords to this particular profile.
I'm passing a parameter foreign key - profile_id - via url, and I have build form from my model Keyword. Now I have problem with passing both of them to my function.
models.py
class Profiles (models.Model):
id_profile = models.AutoField(primary_key=True)
user_id = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=45)
description = models.TextField(max_length=120, default=' ')
class Keywords (models.Model):
id_keyword = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
id_profile = models.ManyToManyField(Profiles)
template
<button class="btn btn-outline-danger" type="submit"> new keyword </button>
urls.py
path('profile_detail/<int:pk>/', users_view.ProfileDetailView.as_view(), name = 'profile_detail'),
path('new_keyword/<profile_id>/', users_view.NewKeyword, name = 'new_keyword'),
views.py
def newKeyword(request):
form = NewKeyword(request.POST)
if request.method == 'POST':
form.is_valid()
form.save()
return redirect('profile')
return render(request, 'users/new_keyword.html', {'form': form})
Now I have
__init__() got an unexpected keyword argument 'profile_id'
I understand that I have to somehow overwrite init() to accept profile_id, but I'm not sure how.
ok, thank you for you answer. I have changed my code, but now I have different problem:The Keywords could not be created because the data didn't validate.
def newKeyword(request):
context = {}
context['id_profile'] = request.POST.get('id_profile', None)
form = NewKeyword(request.POST or None)
if request.method == 'POST':
form.is_valid()
obj = form.save(commit=False)
obj.save()
obj.id_profile.add(context['id_profile'])
obj.save()
return redirect('profile')
return render(request, 'users/new_keyword.html', {'form': form})
and template in previous page:
<form method="POST" name="newkeyword" value='keyword'>
<fieldset class="form-group">
<legend class="border-bottom mb-4"> New keyword </legend>
{{ form | crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-danger" type="submit">create</button>
</div>
</form>
Looks like after clicking a button on page profile (in order to go to view: creating keyword) I use method POST to send parameter, hence my 'If statement' runs...
You are trying to retrieve GET data.
def newKeyword(request, profile_id):
and you can retrieve that parameter with this way but for POST data,
you can use;
request.GET.get('profile_id') # GET method
request.POST.get('profile_id') # POST method
and you don't need to edit url for this.
related question:
Passing variable from django template to view

quick fill forms in django

Sorry I have a really basic question while learning Django and could not find an easy answer.
My model is :
class Entry(models.Model):
name = models.CharField(max_length=200)
type = models.CharField(max_length= 200)
date = models.DateTimeField(auto_now= False, auto_now_add=True)
updated = models.DateTimeField(auto_now= True, auto_now_add= False)
description = models.TextField()
And so my general form implementation is :
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['name','type', 'description']
views:
def add(request):
if request.method == 'POST':
form = EntryForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect('/')
else:
form = EntryForm()
return render(request, "form.html", {'form': form})
I want to add a quick fill button next to add button (that calls above view ) where the name and type is statically filled in the object and only textbox appears for description field.
I could not find a way to statically assign the values to my field in Django.
I had tried creating a different HTML file ( quickform.html) but {{form.as_p}} will put all the fields.
my forms.html is
<form method="POST">
{% csrf_token %}
{{form.as_p}}
<button class="btn btn-success" type='submit'>Submit</button>
</form>
what would be the best way to add a quick link to my index page where the name ( is auto-filled to the "general"+str(id)) and type is auto-filled to "general") is auto-filled and does not appear in the form page
In my opinion the easiest way to do such a thing is to add on click event to your second button then write a simple javascript function the fills the elements you want with static values.

django - creating a preview page

i have a 'Add Post' page in my blog app. and i wanna add a 'preview page'. i should click preview button even before save the post. for this ; i ve created two new fields in my models.py which are : titlePreview and bodyPreview.
what i am doin is saving form datas into these two fields to preview the page before publish.
but i couldn't do that. i have a addPost view. here it is :
#login_required(login_url='/panel')
def addpost(request):
if request.method=="POST":
form = addForm(request.POST)
if form.is_valid():
titleform=form.cleaned_data['title']
bodyform=form.cleaned_data['body']
checkform=form.cleaned_data['isdraft']
owner = request.user
n = Post(title = titleform, body = bodyform, isdraft=checkform, owner=owner)
n.save()
return HttpResponseRedirect('/admin/')
else:
form=addForm()
return render(request,'add.html',{'form':form,})
return render(request,'add.html',{'form':form,})
here is my models.py:
class Post(models.Model):
owner = models.ForeignKey(User)
title = models.CharField(max_length = 100)
body = models.TextField()
bodyPreview = models.TextField()
titlePreview = models.CharField(max_length=100)
slug = AutoSlugField(populate_from='title',unique=True)
posted = models.DateField(auto_now_add=True)
isdraft = models.BooleanField(default=False)
def __unicode__(self):
return self.title
#permalink
def get_absolute_url(self):
return ('view_blog_post',None, {'postslug':self.slug})
now i think i should create a different view like 'preview_view' which must redirects to 'preview.html' from addpost.html.
but i couldnt connect the points.
thank you
Explanation of my previous comments
First part: Creating page template passes the blog post data via POST
This part can be implemented in many ways, this is one with a form:
<form action={% url "preview_page" %} method="POST">
<input type="hidden" name="title" value="{{ post_title }}">
<input type="hidden" name="body" value="{{ post_body }}">
<input type="submit" name="preview_button" value="preview">
</form>
Second part: Create the view preview_page
#login_required(login_url='/panel')
def previewpost(request):
if request.method=="POST":
# Here pass the POST data to the template, POST is already a dict,
# so it may be can fit directly into the template if names match
# though it is always better to check the values first
return render(request,'post_template.html', request.POST)
else:
...
post_template.html
...
<h1>{{ title }}</h1>
<p>{{ body }}</p>
...
Disclaimer: The code is not tested at all
The downside of this solution is that you have to actually pass the data (title, body) again from the client to the server. I hope this works for you.

Django and users entering data

I am building a webapp which will be used by a company to carry out their daily operations. Things like sending invoices, tracking accounts receivable, tracking inventory (and therefore products). I have several models set up in my various apps to handle the different parts of the web-app. I will also be setting up permissions so that managers can edit more fields than, say, an office assistant.
This brings me to my question. How can I show all fields of a model and have some that can be edited and some that cannot be edited, and still save the model instance?
For example, I have a systems model for tracking systems (we install irrigation systems). The system ID is the primary key, and it is important for the user to see. However, they cannot change that ID since it would mess things up. Now, I have a view for displaying my models via a form using the "form.as_table". This is efficient, but merely spits out all the model fields with input fields filling in the values stored for that model instance. This includes the systemID field which should not be editable.
Because I don't want the user to edit the systemID field, I tried making it just a label within the html form, but django complains. Here's some code:
my model (not all of it, but some of it):
class System(models.Model):
systemID = models.CharField(max_length=10, primary_key=True, verbose_name = 'System ID')
systemOwner = models.ForeignKey (System_Owner)
installDate = models.DateField()
projectManager = models.ForeignKey(Employee, blank=True, null=True)
#more fields....
Then, my view for a specific model instance:
def system_details(request, systemID):
if request.method == 'POST':
sysEdit = System.objects.get(pk=systemID)
form = System_Form(request.POST, instance=sysEdit)
if form.is_valid():
form.save()
return HttpResponseRedirect('/systems/')
else:
sysView = System.objects.get(pk=systemID)
form = System_Form(instance=sysView)
return render_to_response('pages/systems/system_details.html', {'form': form}, context_instance=RequestContext(request))
Now the html page which displays the form:
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save Changes">
<input type="button" value="Cancel Changes" onclick="window.location.href='/systems/'">
</form>
So, what I am thinking of doing is having two functions for the html. One is a form for displaying only those fields the user can edit, and the other is for just displaying the content of the field (the systemID). Then, in the view, when I want to save the changes the user made, I would do:
sysValues = System.objects.get(pk=SystemID)
form.save(commit = false)
form.pk = sysValues.sysValues.pk (or whatever the code is to assign the sysValues.pk to form.pk)
Is there an easier way to do this or would this be the best?
Thanks
One thing you can do is exclude the field you don't need in your form:
class System_Form(forms.ModelForm):
class Meta:
exclude = ('systemID',)
The other is to use read-only fields: http://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields as #DTing suggessted
To make a field read only you can set the widget readonly attribute to True.
using your example:
class System_Form(ModelForm):
def __init__(self, *args, **kwargs):
super(System_Form, self).__init__(*args, **kwargs)
self.fields['systemID'].widget.attrs['readonly'] = True
class Meta:
model = System
or exclude the fields using exclude or fields in the class Meta of your form and display it in your template if desired like so:
forms.py
class System_Form(ModelForms):
class Meta:
model = System
exclude = ('systemID',)
views.py
def some_view(request, system_id):
system = System.objects.get(pk=system_id)
if request.method == 'POST':
form = System_Form(request.POST, instance=system)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = System_Form(instance=system)
context = { 'system':system,
'form':form, }
return render_to_response('some_template.html', context,
context_instance=RequestContext(request))
some_template.html
<p>make changes for {{ system }} with ID {{ system.systemID }}</p>
<form method='post'>
{{ form.as_p }}
<input type='submit' value='Submit'>
</form>