I am trying to create a simple CRUD with ModelForm. It works fine except that every time I edit, saving creates a new instance of the data. So i edit and get an extra row in DB instead of an updated one. I am at a loss as to how it knows to save an existing charity as it does not store the PK (id) as a hidden field in the form. That is how I always did it before trying to use the 'fabulous' ModelForm!
It's driving me nuts, I have read everything and as far as I can tell I am doing everything right.
Here is my code..
Model:
from django.db import models
from django.conf import settings
COUNTRY_CHOICES = settings.COUNTRIES
class Charities(models.Model):
charity_name = models.CharField(max_length=100)
country = models.CharField(max_length=4, choices=COUNTRY_CHOICES)
registration_number = models.CharField(max_length=100)
address1 = models.CharField(max_length=100)
address2 = models.CharField(max_length=100)
city = models.CharField(max_length=30)
zip = models.CharField(max_length=10)
phone = models.CharField(max_length=20)
email = models.EmailField()
charity_logo_image = models.CharField(max_length=100)
charity_banner_image = models.CharField(max_length=100)
charity_accepted = models.IntegerField()
def __str__(self):
return self.charity_name
def __unicode__(self):
self.charity_name
View:
def list(request):
charities = Charities.objects.all()
return render_to_response('charities_charity_list.html', {'charities': charities})
def add(request):
return add_or_edit(request)
def edit(request, charity_id):
return add_or_edit(request, charity_id)
def add_or_edit(request, charity_id=None):
print "ID = " + str(charity_id)
form = CharityForm(request.POST or None,
instance=charity_id and Charities.objects.get(pk=charity_id))
# Save new/edited student
if request.method == 'POST' and form.is_valid():
print form
form.save()
return HttpResponseRedirect('/charities/list/')
return render_to_response('charities_charity_edit.html', {'form': form})
Form:
class CharityForm(ModelForm):
class Meta:
model = Charities
Template:
{% extends "base.html" %}
{% block title %}Charities Add{% endblock %}
{% block content %}
<form method="post" action="/charities/add/" id="save"><table cellpadding="0">{{ form.as_table}}</table><input type="submit" value="Save"></form>
{% endblock %}
It doesn`t work because your template is always POSTing to the view that adds a new Charity. When you manually type a URL like /charities/edit/5, it creates the ModelForm with the right initial data, but then POSTs to /charities/add, thus creating a new instance. You need to POST to /charities/edit/5, for example. Take a look at the url template tag.
I suggest you use 2 templates, one for adding, another for editing. I know it may not be very DRY, but I believe it's clearer this way.
Add template:
{% extends "base.html" %}
{% block title %}Charities Add{% endblock %}
{% block content %}
<form method="post" action="{% url charities_app.views.add %}"><table cellpadding="0">{{ form.as_table}}</table><input type="submit" value="Save"></form>
{% endblock %}
Edit template:
{% extends "base.html" %}
{% block title %}Edit Charity{% endblock %}
{% block content %}
<form method="post" action="{% url charities_app.views.edit charity.id %}"><table cellpadding="0">{{ form.as_table}}</table><input type="submit" value="Save"></form>
{% endblock %}
You may also want to check the create_object and update_object generic views, they are very useful in simple cases like yours.
Related
I wanted to create simple datepicker that does not accept back dates. Within my models.py I have defined MealDay class and standalone functionvalidate_pub_date.
The logic behin works just fine, but I do not understand the way Django is showing up the ValidationError("Date can't be past!").
Why this is where it is, and why it seems to be within <li> tag? Is there any possibilty to handle the error within the HTML template or any other way to add some html/css to it? There is how the error looks now:
models.py:
def validate_pub_date(value):
if value < timezone.now() - datetime.timedelta(days=1):
raise ValidationError("Date can't be past!")
return value
class MealDay(models.Model):
day = models.DateTimeField(default=timezone.now().day, validators = [validate_pub_date])
breakfast = models.TextField(max_length=100, blank=True)
lunch = models.TextField(max_length=100)
dinner = models.TextField(max_length=100, blank=True)
views.py
class MealdayCreateView(CreateView):
model = MealDay
template_name = "mealplanner/mealday_new.html"
form_class = CreateMealdayForm
forms.py
class CreateMealdayForm(ModelForm):
class Meta:
model = MealDay
fields = '__all__'
widgets = {
'day': forms.DateInput(attrs={'type':'date'}),
}
mealday_new.html
{% extends "mealplanner/base.html" %}
{% block content %}
<h1>Plan your meals!</h1>
<form action="" method="post"> {% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save">
</form>
{% endblock content %}
{% endblock content %}
in django model (validate_field_name) method is connected with .is_valid() method so when all fields of modelform not get correct input till it's raise a validation error.
I'm a newbie at extracting values from the DB via views and templates but all of my attempts have failed so far. I've been looking at this for several hours now.
I have the below model in my users app at models.py. This is an additional model to the "main one" with the regular name, email and password for my users.
class WorkEmail(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True)
work_email = models.EmailField(unique=False, null=True, blank=True)
work_email_verified = models.BooleanField(default=False)
work_email_active = models.BooleanField(default=False)
verified_company_name = models.CharField(max_length=100, null=True, blank=True)
company_url = models.URLField(max_length=100, null=True, blank=True)
request_datetime = models.DateTimeField(blank=True, null=True, auto_now_add=True, auto_now=False)
def __str__(self):
return self.work_email
I have this UpdateView in views.py that works perfectly (with the exception of being able to see whether the work email has been verified or not, i.e. from the line with work_is_verified, till the end.
class UpdateProfileView(UpdateView):
form_class = CustomUserChangeForm
success_url = reverse_lazy('home')
template_name = 'update_profile.html'
def get_object(self, queryset=None):
return self.request.user
def work_is_verified(self, request):
if request.work_email_verified==True and request.work_email_active==True:
return True
else:
return False
And I have the below, in my update profile template at update_profile.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Home{% endblock title %}
{% block content %}
{% if user.is_authenticated %}
<h2>Profile</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Update</button>
</form>
<h2>Verification </h2>
<p> {{user.work_is_verified}} </p>
{% if user.work_is_verified == True %}
Delete Work Email and Verified Company Name
{% else %}
Verify Company Name via Work Email
{% endif %}
<p></p>
{% else %}
<p>You are not logged in</p>
Log In |
Sign Up
{% endif %}
{% endblock content %}
Template works and I'm able to change the values of my main form (customuserchangeform) BUT that method that I created in my view to check if the email is verified and active for a user is not doing anything... (not even showing up).
I want to be able to make a logic that gives either a True or a False whenever we're checking that a user's records in that table show the email being both, verified and active. I should say that this model/table could have many rows for the same user, but the way we can identify the most up-to-date work email is by filtering by both, work_email_active and work_email_verified.
{{user.work_is_verified}}
{% if user.work_is_verified == True %}
Thanks so much in advance.
Try this
For email in request.user.workmail_set:
if email.workmail.work_email_verified and email.workmail.work_email_active:
return True
else:
return False
I am using Tweepy to post a tweet from Django admin. How can I retrieve "title" and "image" from the model object as shown in the picture, whenever I click on the tweet button it should post a tweet on my Twitter account with title as text and image.
Example: Suppose I have 4 model objects and each object has a title and an image, I want to get the title and image of only that object which I want to post on Twitter.
One thing I can do is pass a primary key or id of that particular model object
but I don't know how to pass it in my template below.
Django Admin Form page
#template
{% extends 'admin/change_form.html' %}
{% load i18n %}
{% block content %}
{{ block.super }}
<form action="{% url 'post_tweet' %}" method="POST">
{% csrf_token %}
<input type="submit" value="Tweet" name="add_tweet">
</form>
{% endblock %}
#urls.py
urlpatterns = [
path('', views.Home.as_view(), name="home"),
path('$', views.tweet, name="post_tweet"),
]
#models.py
class Movie(models.Model):
GENRE = (
('Action', 'Action'),('Adventure', 'Adventure'),('Comedy', 'Comedy'),
('Crime', 'Crime'),('Drama', 'Drama'),('Fantasy', 'Fantasy'),
('Horror', 'Horror'),('Mystery', 'Mystery'),('Science Fiction', 'Science Fiction'),
('Superhero', 'Superhero'),('Thriller', 'Thriller'),('War','War'),('Others','Others')
)
image = models.ImageField(upload_to="images/",blank=True, default="default-image.png",null=True)
title = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=100, allow_unicode=True, unique=True, default='',blank=True)
genre = models.CharField(max_length=30, choices=GENRE)
description = models.TextField(max_length=500, blank=True)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super().save(*args,**kwargs)
class Meta:
ordering = ["-title"]
# views.py
def tweet(request):
if request.method == "POST":
twitter_auth_keys = {
"consumer_key" : "XXX",
"consumer_secret" : "XXX",
"access_token" : "XXX",
"access_token_secret" : "XXX"
}
auth = tweepy.OAuthHandler(
twitter_auth_keys['consumer_key'],
twitter_auth_keys['consumer_secret']
)
auth.set_access_token(
twitter_auth_keys['access_token'],
twitter_auth_keys['access_token_secret']
)
api = tweepy.API(auth)
`Do something here`
tweet = "New"
post_result = api.update_status(status=tweet)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
You can print the object_id value in your template, that value is passed by default by the change_view method, you can always override this method if you want to pass extra data to your change_view template too: https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.change_view
So one way to do what you want to do is this, you can print the object_id in a hidden input so the value will be passed to your view by the form submit:
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block content %}
{{ block.super }}
<form action="{% url 'post_tweet' %}" method="POST">
{% csrf_token %}
<input type="hidden" name="movie_id" value="{{ object_id }}">
<input type="submit" value="Tweet" name="add_tweet">
</form>
{% endblock %}
Now you can get the instance of the object and get the title and the image of a movie in your tweet view by searching by its id.
I am trying to display a checklist in the CreateView using the values in the ForeignKey fields for descriptions.
models.py
class Structure(models.Model):
name = models.CharField(max_length = 30)
description =models.CharField(max_length = 300, null=True, blank=True)
def __str__(self):
return self.name
class SelectedFramework(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
structure = models.ForegignKey(Structure)
selected = models.BooleanField(default = False)
views.py
class FrameworkCreateView(generic.CreateView):
model = SelectedFramework
fields =['structure', 'selected']
template_name = 'catalogue/structure.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super(FrameworkCreateView, self).form_valid(form)
structure.html
{% extends 'catalogue\base.html' %}
{% block container %}
<h2>{% block title %}Structures{% endblock title %}</h2>
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<div class="col-sm-10">{{form.structure}} {{form.selected}}</div><br>
{% endfor %}
</div>
</form>
{% endblock %}
The code above works but will display the ForeignKey 'structure' as a dropdown list with the values of __str__. Is there a way to display string for structure.name and structure.description with the checkbox from selected in the CreateView?
In your template use:
{{ form.structure.name }}
{{ form.structure.description}}
You can write custom form, override the save method and create Structure object manually there:
class FrameworkForm(forms.ModelForm):
structure_name = forms.CharField(required=True)
structure_description = forms.CharField(required=False)
class Meta:
model = SelectedFramework
fields = [
'structure_name', 'structure_description', 'selected'
]
def save(self, commit=False):
instance = super(FrameworkForm, self).save(commit=False)
structure = Structure(
name=self.cleaned_data.get('structure_name'),
description=self.cleaned_data.get('structure_description')
)
structure.save()
instance.structure = structure
instance.save()
return instance
Also add form_class = FrameworkForm to your view instead of fields = ['structure', 'selected']
EDIT:
Perhaps you want something like this:
<ul>
{% for structure in form.fields.structure.choices.queryset %}
<li>{{ structure.name }} - {{ structure.description }}</li>
{% endfor %}
</ul>
If you want to get fields by iterating in the template. You have to use-
{% for field in form %}
{{ field }}
{% endfor %}
don't have to use any dot notation to get the field. If you want to get the label of the field you can use {{ field.label}} usually before {{field}}
EDIT: I fixed the views.py with Av4t4r's code but it still shows no content. When I type something in voodoo.html it actually shows content, but all of the content inside the {% block content %} are not showing. Why is that?
Hello I am trying to make a simply gallery app where the first view (listview) is a list of all the persons (which are the objects), and when a user clicks on one it proceeds to the next page with a given pk/id key. But when it comes to that page... the content is blank. Here is what I have:
urls.py:
urlpatterns = [
url(r'^$', ListView.as_view(queryset=Images.objects.all(), template_name='imgboard/home.html')),
url(r'^imgboard/(?P<id>\d+)/$', views.voodoofunction, name='voodoofunction'),
]
views.py (I feel like this is where the problem is):
def voodoofunction(request, id=None):
instance = get_object_or_404(Moreimages, id=id)
context = { "object_list": instance, }
return render(request, "imgboard/voodoo.html", context)
models.py
class Images(models.Model):
name_person = models.CharField(max_length=70)
instagram = models.CharField(max_length=200)
img_url = models.CharField(max_length=500)
def __unicode__(self):
return self.name_person
class Meta:
verbose_name_plural = 'Images'
class Moreimages(models.Model):
key = models.ForeignKey(Images, on_delete=models.CASCADE)
img_url = models.CharField(max_length=500)
def __unicode__(self):
return str(self.key)
class Meta:
verbose_name_plural = "More Images"
listview_code.html
{% block content %}
{% for object in object_list %}
<p>{{object.name_person}}</p>
{% endfor %}
{% endblock %}
voodoo.html:
{% block content %}
<h2>{{ object.name_person}}<br></h2>
<h4>{{object.instagram}}</p></h4>
<br>
{% for object in object_list %}
<p><img src="{{object.img_url}}", width=350, height="360></img>"</p>
{% endfor %}
{% endblock %}
Your context has no "object" in it. And your "object_list" is an instance, not a list (so your for loop is doing nothing).