I have a page that basically displays user-entered data based on a project. The initial page contains just a handful of fields one being the project_name, which I have set as unique
project_name = models.CharField(max_length=50, blank=False, unique=True)
From this page, I want to open up another page based on the project_name which enables additional details to be added and stored in a different model.
Models.py
class Project(models.Model):
project_name = models.CharField(max_length=50, blank=False, unique=True)
project_website = models.URLField(max_length=50, blank=True)
project_description = models.TextField(blank=True)
ckeditor_classic = models.TextField(blank=True)
def __str__(self):
return str(self.project_name)
class Fundamentals(models.Model):
project_name = models.ForeignKey(Project, to_field='project_name', on_delete=models.CASCADE)
project_website = models.URLField(max_length=100, blank=True)
project_roadmap = models.CharField(max_length=25)
def __str__(self):
return str(self.project_name)
What I am struggling to do is load the second page but displaying the project_name and be able to associate the next set of data to the project
I think i need to do this via the views and urls but I can't get it to work.
View.py
#login_required
def AddFundamentals(request,project_id):
project = Project.objects.get(pk=project_id)
form = AddFundamentalsForm(request.POST or None, instance=project)
if form.is_valid():
form.save()
return redirect('dahsboard.html')
return render(request, 'pages/critical_fundementals.html', {'project': project, "form": form})
The error returned is
AddFundamentals() missing 1 required positional argument: 'project_id'
but I am passing this in via the URL
path('fundamentals/<project_id>', view=AddFundamentals, name="add_fundamentals"),
Do I need to do more in the view to return the data based on the PK? I'm really struggling with this :(
Update: Added Forms.PY and Link
class AddFundamentalsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AddFundamentalsForm, self).__init__(*args, **kwargs)
class Meta:
model = Fundamentals
fields = '__all__'
<a class="dropdown-item" href="{% url 'add_fundamentals project.id ' %}">Critical Fundamentals</a>
Thanks #Hills.
This resolved my issue path('fundamentals/<int:project_id>/'
I had to remove the trailing / and all sprung into life :)
Thanks everyone.
Related
I'm hoping somebody can help me with this issue. Im having trouble adding my page/ article title to the url path. I've tried a number of ways can't seem to get it. If anyone could help that would be great.
My current Url path is "https://stackoverflow.com/article/1"
Would like it to be "https://stackoverflow.com/article/1/example-question-help", or some variation of that.
Below you can find how my views and url files are set up.
<a href="{% url 'article-detail' post.pk %}"
path('article/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),'
class ArticleDetailView(DetailView):
model = Post
template_name = 'article_detail.html'
def get_context_data(self, *args, **kwargs):
cat_menu = Category.objects.all()
stuff = get_object_or_404(Post, id=self.kwargs['pk'])
total_likes = stuff.total_likes()
liked = False
if stuff.likes.filter(id=self.request.user.id).exists():
liked = True
context = super(ArticleDetailView, self).get_context_data(*args, **kwargs)
context["cat_menu"] = cat_menu
context["total_likes"] = total_likes
context["liked"] = liked
return context
class Post(models.Model):
title = models.CharField(max_length=250)
header_image = models.ImageField(null=True, blank=True, upload_to="images/")
title_tag = models.CharField(max_length=250, default='none')
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
slug = models.SlugField(null=True)
# body = models.TextField()
post_date = models.DateField(auto_now_add=True)
category = models.CharField(max_length=250, default='')
snippet = models.CharField(max_length=250)
likes = models.ManyToManyField(User, related_name='blog_posts')
Assuming that your model has a slug field, called slug, which it looks like it may given your request, you'd change things like this;
path('article/<slug:slug>/', ArticleDetailView.as_view(), name='article-detail'),'
Django will then do the rest because SingleObjectMixin which is used by DetailView looks at the URL first for a primary key, then for a slug.
So this will give you URLs that look like;
https://stackoverflow.com/article/example-question-help
You can define a path that includes both the primary key and the slug:
path(
'article/<int:pk>/<slug:slug>/',
ArticleDetailView.as_view(),
name='article-detail',
),
This will automatically filter the item properly. In the link you then pass both the primary key and slug:
<a href="{% url 'article-detail' post.pk post.slug %}">
You can boost efficiency by determining the number of likes and whether the object is liked all in the same queryset with an Exists subquery [Django-doc]:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Exists, OuterRef
class ArticleDetailView(LoginRequiredMixin, DetailView):
model = Post
template_name = 'article_detail.html'
queryset = Post.objects.annotate(total_likes=Count('likes'))
def get_queryset(self, *args, **kwargs):
super().get_queryset(*args, **kwargs).annotate(
is_liked=Exists(
Post.likes.through.objects.filter(
post_id=OuterRef('pk'), user=request.user
)
)
)
def get_context_data(self, *args, **kwargs):
return super().get_context_data(
*args, **kwargs, cat_menu=Category.objects.all()
)
In the modeling, you might want to work with an AutoSlugField [readthedocs.io] from the django-autoslug package [readthedocs.io] to automatically slugify. Otherwise you will have to do this yourself. It also makes not much sense that the slug field is NULLable: normally a record will always have a slug. You thus might want to refactor the model to:
from autoslug import AutoSlugField
from django.conf import settings
class Post(models.Model):
title = models.CharField(max_length=250)
header_image = models.ImageField(null=True, blank=True, upload_to='images/')
title_tag = models.CharField(max_length=250, default='none')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
slug = models.AutoSlugField(populate_from='title')
post_date = models.DateField(auto_now_add=True)
category = models.CharField(max_length=250, default='')
snippet = models.CharField(max_length=250)
likes = models.ManyToManyField(
settings.AUTH_USER_MODEL, related_name='liked_posts'
)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
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!
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.
I have a list of employees who work at a site. Each site is owned by a User (using Django's standard user model).
I want to create a form that adds an employee and automatically links them to a site dependent on who the authenticated user is:
models.py:
class Employee(models.Model):
site = models.ForeignKey(Site, null=True)
employee_name = models.CharField(default='name', max_length=128, blank=False, null=False)
class Site(models.Model):
user = models.ForeignKey(User)
site_name = models.CharField(max_length=128, blank=False, null=False)
views.py:
site_profile = Site.objects.get(user=request.user)
if request.method == "POST":
form = EmployeeAddForm( request.POST )
if form.is_valid():
obj = form.save(commit=False)
obj.site = site_profile
obj.save()
return redirect('dashboard_home')
form = EmployeeAddForm()
return render(request, "dashboard/employees.html", {'form': form })
forms.py:
class EmployeeAddForm(forms.ModelForm):
class Meta:
model = Employee
fields = ( 'employee_name')
This code will add the employee to the database, but in django admin, list_display = 'site' results in Site object not the actual site name. It does not appear that the employee is linked to the site.
If I use obj.site = site_profile.id (adding .id), I get the error Cannot assign "1": "Employee.site" must be a "Site" instance.
Found the error: the above code is correct, I simply had a tab ordering error in my Site modeL
class Site(models.Model):
...
def __str__(self):
return self.site_name
def should have been inserted 1 tab inwards.
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.