Related
I am using a Detail View generic and I am trying to add an attribute to each item for a group of Comments (The detail view is for a Post that has many comments). I am trying to loop through the group of comments and assign a boolean to a new attribute is_liked_by_user. When I try to print comment.is_liked_by_user it will show in the scope of it but when I try to find it in the context['post'] I get this error *** AttributeError: 'Comment' object has no attribute 'is_liked_by_user'. What is the correct way to assign this attribute so that it can be passed to my template?
class DetailView(LoginRequiredMixin,generic.DetailView):
model = Post
template_name = 'posts/detail.html'
form_class = CommentsForm
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super(DetailView, self).get_context_data(**kwargs)
# Create any data and add it to the context
context['form'] = self.form_class
post = context['post']
comments = post.comment_set.all()
for i, comment in enumerate(comments):
comment.is_liked_by_user = check_existing_dictionary_in_list(comment.reactions.all(), "user", self.request.user)
print("Was comment liked by user?", comment.is_liked_by_user, comment.comment_body)
# comment.is_liked_by_user = check_existing_dictionary_in_list(comment.reactions.all(), "user", self.request.user)
# context['is_liked_by_user'] = check_existing_dictionary_in_list(post.reactions.all(), "user", self.request.user)
pdb.set_trace()
return context
class Comment(models.Model):
comment_body = models.CharField(max_length=200)
like_count = models.IntegerField(default=0)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE, null = True)
created_date = models.DateTimeField('date created', null=True, auto_now_add=True,blank=True )
reactions = GenericRelation(Reaction)
def __str__(self):
return self.comment_body
I am utterly confused and mortified by CBVs, seeking for help.
So I've designed the model structure and decided the url patterns as following, but simply can't write a valid CBV to facilitate the urls:
models.py
class Category(models.Model):
'''Category for men's and women's items'''
men = models.BooleanField()
women = models.BooleanField()
name = models.CharField(max_length=100)
description = models.CharField(max_length=300, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta():
verbose_name_plural = 'Categories'
def __str__(self):
return ("Men's " + self.name) if self.men else ("Women's " + self.name)
class SubCategory(models.Model):
'''Sub-category for the categories (not mandatory)'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.CharField(max_length=300, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta():
verbose_name = 'Sub-category'
verbose_name_plural = 'Sub-categories'
def __str__(self):
return ("Men's " + self.name) if self.category.men else ("Women's " + self.name)
class Item(models.Model):
'''Each item represents a product'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
subcategory = models.ForeignKey(
SubCategory, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.IntegerField(default='0')
discount = models.IntegerField(null=True, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta:
ordering = ['-uploaded_date']
def __str__(self):
return self.name
def discounted_price(self):
'''to calculate the price after discount'''
return int(self.price * (100 - self.discount) * 0.01)
class ItemImage(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(upload_to='itemimages', null=True, blank=True)
urls.py
app_name = 'boutique'
urlpatterns = [
# show index page
path('', views.IndexView.as_view(), name='index'),
# show categories of products for men or women
path('<slug:gender>/', views.ItemListView.as_view(), name='show-all'),
# show a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/', views.ItemListView.as_view(), name='category'),
# show a specific subcategory under a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/subcat_<int:subcategory_pk>/', views.ItemListView.as_view(), name='subcategory'),
# show a specific item
path('item_<int:item_pk>/', views.ItemDetailView.as_view(), name='item'),
]
views.py
class IndexView(ListView):
'''landing page'''
model = Category
template_name = 'boutique/index.html'
context_object_name = 'categories'
class ItemListView(ListView):
'''display a list of items'''
# model = Category ??? what's the point of declaring model when get_context_data() ???
template_name = 'boutique/items.html'
context_object_name = 'categories'
paginate_by = 12
def get_object(self):
obj = get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all() # for rendering nav bar data
return context
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
context_object_name = 'item'
def get_object(self):
return get_object_or_404(Item, pk=self.kwargs['item_pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
items.html
<a href="{% url 'boutique:show-all' 'women' %}> link to categories of product for women </a>
<a href="{% url 'boutique:category' 'women' category.pk %}> link to a cat for women </a>
<a href="{% url 'boutique:subcategory' 'women' category.pk subcategory.pk %}> link to a subcat under a specific cat for women </a>
Essentially, as you can see, I'd like the ItemListView to render multiple url paths depending on what value passed into the CBV... I can do it (pass multiple values) in a FBV, however, utterly confused by the mechanism of the CBVs...
So if anyone could write an example ItemListView and according anchor url template tags (if mine are incorrect), it would be tremendulent!!! Thanks!!!
EDIT 1
class ItemListView(ListView):
'''display a list of items'''
model = Item
template_name = 'boutique/items.html'
# paginate_by = 12
def get_queryset(self):
# get original queryset: Item.objects.all()
qs = super().get_queryset()
# filter items: men/women
if self.kwargs['gender'] == 'women':
qs = qs.filter(category__women=True)
elif self.kwargs['gender'] == 'men':
qs = qs.filter(category__men=True)
if self.kwargs.get('category_pk'):
qs = qs.filter(category=self.kwargs.get('category_pk'))
if self.kwargs.get('subcategory_pk'):
qs = qs.filter(subcategory=self.kwargs.get('subcategory_pk'))
# print(qs)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
context['categories'] = Category.objects.all()
if self.kwargs.get('gender') == 'women':
context['category_shown'] = Category.objects.filter(women=True)
if self.kwargs.get('gender') == 'men':
context['category_shown'] = Category.objects.filter(men=True)
if self.kwargs.get('category_pk'):
context['category_shown']=get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
if self.kwargs.get('subcategory_pk'):
context['subcategory_shown']=get_object_or_404(SubCategory, pk=self.kwargs.get('subcategory_pk'))
# print(context)
return context
After FiddleStix' answer (I haven't gone thu bluegrounds thread yet), I tried and managed to make everything work except ItemDetailView.
The urls are working fine and the filtering of get_queryset function is working fine, however,
Question 1: I wonder this might not be DRY enough?! Nevertheless, it's working. so thanks!! But could it be dryer??
Question 2: when ItemDetailView runs, the urls appear to be correct, however, the page redirect to a page rendering all items from all categories...
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
def get_object(self):
print(get_object_or_404(Item, pk=self.kwargs.get('item_pk')))
return get_object_or_404(Item, pk=self.kwargs.get('item_pk'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
# context['categories'] = Category.objects.all()
print(context)
return context
Neither is the object nor the context being printed out... and it doesn't prompt any error, either. I must have made a silly mistake somewhere!!
Answer to Question 2:
view.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
The DetailView class should be simple if no customisation is needed, the problem is in the urlpatterns:
url.py
urlpatterns = [
path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]
Always use <pk> as the value passed in DetailView as it's the default. I used item_<int:item_pk> as the path url. That's why I had to use get_object() to manually get the item object (to override the default get_object()). As #bluegrounds answer suggests that the reason Class-based Views work well is they save time, for the default functions they possess.
If you wish to use item_<int:item_pk>, CBV offers the flexibility as well: simply override pk_url_kwargs = 'item_pk' in the View class - Feel free to check my other question: DetailView - get_object function confusion for clarification. #neverwalkaloner 's answer is very straightforward.
So first things first, CBVs...
They work by having "default" behaviours built into each one of them. For the ListView class, take this example view:
class ArticleListView(ListView):
model = Article
along with an example urlpattern:
path('all-articles/', ArticleListView.as_view())
and that's it. This is all that is essential for a ListView to work. This ArticleListView view would look for a template called article_list.html and in it you can use the context variable object_list to access all the Article objects that the class gets for you without you having to write the QuerySet explicitly.
Of course you can change these values, and customize the QuerySet, and do all kinds of things, but for that you'll have to study the docs. I personally find ccbv much easier to read than the docs. So for example you can see in ccbv's page about ListViews that the context_object_name = None which defaults to object_list, as mentioned above. You can change that to, for example context_object_name = 'my_articles'. You could also set the template_name = 'my_articles.html' and that will override the default template name pattern of <model>_list.html.
Now, about your code,
If you're sure that you want your URL structure to stay like it is, you could have your class view as follows to get the functionality you need:
class ItemListView(ListView):
template_name = 'boutique/items.html'
context_object_name = 'categories'
paginate_by = 12
def get_queryset(self):
# This method should return a queryset that represents the items to be listed in the view.
# I think you intend on listing categories in your view, in which case consider changing the view's name to CategoryListView. Just sayin'...
# An instance of this view has a dictionary called `kwargs` that has the url parameters, so you can do the following:
# You need some null assertions here because of the way you've setup your URLs
qs = Categories.objects.filter(men=self.kwargs['gender'], pk=self.kwargs['category_pk'])
return qs
As you can see, we didn't set many things in this class view for it to work. Namely, we didn't set the model variable as we did previously. That's because we wouldn't need it. The part that uses that variable was in the default get_queryset() method and we've overridden that method. See CCBV for more info on the default implementation of get_queryset().
Now the template will be supplied with the objects from get_queryset(), under the name categories, because that's what we set context_object_name's value to be.
NOTE: The variable model is used in other places other than get_queryset() such as the default template_name. The default template name is derived from the model name and the template_name_suffix. So if you don't set the model variable, make sure to set the template_name manually.
I'm not sure about your application's logic but I think you should change the Category model to have only one Boolean field that denotes gender. For example men if it is True and women if it's False. That way a category can't be for both men and women at the same time (unless that is something you need), and it also can't be for neither, because currently you can have a category be false for the both gender fields, which doesn't really make sense.
I would actually suggest a completely different solution that involves CHOICES, as such:
gender = models.IntegerField(null=False, CHOICES=[(1,'Men'), (2,'Women'), (3,'Other'), (4,'Unisex')], default=3)
This would store a number in the database that denotes the gender, and in your app you'll only see the correlating string (gender) to that number.
I have not tried this code on my machine so I might have missed a few things, but I hope I clarified the overall workings of CBVs.
To answer you main question, your ItemList should set model=models.Item, as alluded to in the error message, because it is meant to be a list of items.
I would set up your urls.py so that /items/ or /item_list/ goes to ItemListView.as_view(). If you then want to filter your list of items, I would not do it by changing the URL to /items/women/. Instead, I would use a URL query string like /items/?gender=women. How to do that is explained here but basically:
class ItemListView(ListView):
model = Item
template_name = "item_list.html"
paginate_by = 100
def get_queryset(self):
filter_val = self.request.GET.get('gender', 'some_default')
queryset = Item.objects.filter(
... # You'll have to work this bit out
)
return queryset
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['gender'] = self.request.GET.get('gender', 'some_default')
return context
I'm reading a book about Django and I'm trying to reinterpret some content. (I'm using Django 2.1 and Python 3.6)
In the book different types of contents are associated with a module, like this:
class Module(models.Model):
course = models.ForeignKey(Course,
related_name='modules',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
order = OrderField(blank=True, for_fields=['course'])
class Meta:
ordering = ['order']
def __str__(self):
return '{}. {}'.format(self.order, self.title)
class Content(models.Model):
module = models.ForeignKey(Module,
related_name='contents',
on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE,
limit_choices_to={'model__in': (
'text',
'video',
'image',
'file')})
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
order = OrderField(blank=True, for_fields=['module'])
class Meta:
ordering = ['order']
class ItemBase(models.Model):
owner = models.ForeignKey(User,
related_name='%(class)s_related',
on_delete=models.CASCADE)
title = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def __str__(self):
return self.title
def render(self):
return render_to_string(
'courses/content/{}.html'.format(self._meta.model_name),
{'item': self})
class Text(ItemBase):
content = models.TextField()
class File(ItemBase):
file = models.FileField(upload_to='files')
class Image(ItemBase):
file = models.FileField(upload_to='images')
class Video(ItemBase):
url = models.URLField()
In the book there are CBVs to generate the right form for content types:
class ContentCreateUpdateView(TemplateResponseMixin, View):
module = None
model = None
obj = None
template_name = 'courses/manage/content/form.html'
def get_model(self, model_name):
if model_name in ['text', 'video', 'image', 'file']:
return apps.get_model(app_label='courses',
model_name=model_name)
return None
def get_form(self, model, *args, **kwargs):
Form = modelform_factory(model, exclude=['owner',
'order',
'created',
'updated'])
return Form(*args, **kwargs)
def dispatch(self, request, module_id, model_name, id=None):
self.module = get_object_or_404(Module,
id=module_id,
course__owner=request.user)
self.model = self.get_model(model_name)
if id:
self.obj = get_object_or_404(self.model,
id=id,
owner=request.user)
return super(ContentCreateUpdateView,
self).dispatch(request, module_id, model_name, id)
def get(self, request, module_id, model_name, id=None):
form = self.get_form(self.model, instance=self.obj)
return self.render_to_response({'form': form,
'object': self.obj})
def post(self, request, module_id, model_name, id=None):
form = self.get_form(self.model,
instance=self.obj,
data=request.POST,
files=request.FILES)
if form.is_valid():
obj = form.save(commit=False)
obj.owner = request.user
obj.save()
if not id:
# new content
Content.objects.create(module=self.module,
item=obj)
return redirect('module_content_list', self.module.id)
return self.render_to_response({'form': form,
'object': self.obj})
And contents are provided by users with special permissions.
Ok and now the question: I want the contents to be managed only by the admin, in the admin site.
I've tried this way:
class ContentInline(GenericStackedInline):
model = Content
extra = 0
#admin.register(Module)
class ModuleAdmin(admin.ModelAdmin):
list_display = ['title', 'order', 'course']
list_filter = ['course']
search_fields = ['title', 'description']
inlines = [ContentInline]
#admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
list_display = ['object_id', 'module', 'content_type', 'order']
but I have not been able to display in the admin site forms the field to upload the right content type.
Module page looks like this:
module page in admin site
While Content page is this:
content page in admin site
I've been searching for a solution for a while but I've not been able to find one. There is a similar topic here: link but suggested solutions imply javascript and/or additional packages while I'd like to do that only with Python and Django.
I've also read that a possible solution to display custom views in admin site is to write a view and then add it to the admin urls, then add it to the admin model.
Someone else suggested to use CBV from the book and use it in model admin.
I've tried to implement these suggestions with no luck.
In the last version of my code I try to use the CBV with as_view() this way (in views.py right after CBV):
content_create_update_view = ContentCreateUpdateView.as_view()
and then in admin.py:
#admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
list_display = ['object_id', 'module', 'content_type', 'order']
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('content/<int:object_id>/change/', self.admin_site.admin_view(content_create_update_view)),
]
return my_urls + urls
any help or suggestion is greatly appreciated.
In the end I was not able to find a solution to my problem, at least in the way I wanted to.
So I tried to get around the problem to get the same result in other ways. While looking for different methods, such as not using the GenericForeignKey or the ContentTypes I came across this link:avoid Django's GenericForeignKey
It seemed clear and simple, although maybe a little 'less elegant, so I implemented the following solution.
1) I modified the Content class by removing the GenericForeignKey and replacing it with a OneToOne relation for each type of supported content:
text = models.OneToOneField(Text, null=True, blank=True, on_delete=models.CASCADE)
file = models.OneToOneField(File, null=True, blank=True, on_delete=models.CASCADE)
image = models.OneToOneField(Image, null=True, blank=True, on_delete=models.CASCADE)
video = models.OneToOneField(Video, null=True, blank=True, on_delete=models.CASCADE)
and to make sure that only one attachment was matched at a time for each content, I added a check overwriting the save function:
def save(self, **kwargs):
assert [self.text, self.file, self.image, self.video].count(None) == 3
return super().save(**kwargs)
Finally I added a property to the class that would return the type of content:
#property
def target(self):
if self.text_id is not None:
return self.text
if self.file_id is not None:
return self.file
if self.image_id is not None:
return self.image
if self.video_id is not None:
return self.video
raise AssertionError("You have to set content!")
2) I modified the admin.py file by adding all the expected types of Content:
#admin.register(Text)
class TextAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
#admin.register(File)
class FileAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
#admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
#admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
list_display = ['title', 'created', 'updated']
Then I modified the ContentAdmin class by overriding the get_queryset function:
#admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(ContentAdmin, self).get_queryset(request)
qs = qs.select_related('text',
'file',
'image',
'video')
return qs
In this way, I am able to load the attachments in the admin interface and then combine them with the desired Content by selecting them conveniently from a list. Unfortunately this is a procedure in 2 steps, I would prefer to solve everything in one form admin side, but... it works.
I have taken a look around, and I didn't find an answer to this question.
Serializers.py
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = ['title', 'body', 'comments', 'user', 'date']
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Comment
fields = ['body', 'user', 'date']
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['id', 'user']
Models.py
class Post(models.Model):
# Children
comments = models.ManyToManyField('Comment', blank=True)
# Content
title = models.TextField(default="-->Title here<--")
body = models.TextField(default="-->Body here<--")
# About
user = models.ForeignKey(User)
date = models.DateTimeField(auto_now=True)
object_id = models.PositiveIntegerField(default=0)
content_type = models.ForeignKey(ContentType, default=0)
content_object = fields.GenericForeignKey()
def __str__(self):
return str(self.title)
class Comment(models.Model):
comments = models.ManyToManyField('Comment', blank=True)
# Content
body = models.TextField(default="-->Body here<--")
# About
user = models.ForeignKey(User)
date = models.DateTimeField(auto_now=True)
object_id = models.PositiveIntegerField(default=0)
content_type = models.ForeignKey(ContentType, default=0)
content_object = fields.GenericForeignKey()
def __str__(self):
return str(self.body)
Views.py
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def index(request):
return render(request, 'index.html', [])
The index template is a "homepage" that loads the latest posts. Under each post, I'm showing the comments. In the JSON those are links, and I found out how to load them trougth the links (so it's working).
Someone told me that instead of doing it this way, I should make it "load" the comments in the backend, and send them together with the posts (the data, not links). He told me to take a look into: http://www.django-rest-framework.org/api-guide/filtering/#overriding-the-initial-queryset
I can't really figure it out.
How do I get the data, insted of links for the ManyToManyField?
To unfold all the related data one level deep you can use depth param:
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = ['title', 'body', 'comments', 'user', 'date']
depth = 1
This will replace post.user and post.comments ids with actual records. depth=2 will also unfold post.comments.user. If you want to selectively pull post.comments only one level deep and not post.user:
class PostSerializer(serializers.HyperlinkedModelSerializer):
comments = CommentSerializer(many=True) #has to be declared higher above
class Meta:
model = Post
fields = ['title', 'body', 'comments', 'user', 'date']
If on top you want to have post.comments.user unfolded, need to either put depth=1 into existing comment serializer or create a new serializer with unfolded users just for this view, similar to examples above.
Also make sure you are using prefetch_related on your queryset or the performance will take a serious hit, like:
Post.objects.all().prefetch_related('comments', 'comments__user')
I am building an FAQ system. The models extend from Topic -> Section -> Article. When creating a new Article the User will select a Topic then a Section then the create Article button.
The url will look something like //mysite.org/Topic_PK/Section_PK/Article_Create
In Django it should look like this:
url(r'^ironfaq/(?P<pk>\d+)/(?P<pk>\d+)/article$', ArticleCreateView.as_view(), name=’article-create’)
What I am looking to do is to associate the Section_PK to the Article when the user submits the Article. I have the Section_PK in the URL I need help to figure out how to use it to do this.
Alternatively with this set up I can have a form rendered with a choice selection from the Section_FK in the Articles Model. If when creating the Article upon rendering the template if I could limit the Section choices by the Topic in the form.py this will also work for my needs
The url will look something like //mysite.org/Topic_PK/article/create
In Django the url should look like this:
url(r'^ironfaq/(?P<pk>\d+)/article/create$', ArticleCreateView.as_view(), name=’article-create’)
Both these methods require the Passing of the Topic or Section PK to the view or form thru the URL. If there is a better way to do this I am open to other suggestions.
In Django I have the following Models
class Topic(Audit):
name = models.CharField(max_length=255)
sort = models.SmallIntegerField()
slug = models.SlugField()
class Meta:
verbose_name_plural = "topics"
def __str__(self):
return self.name
def get_absolute_url(self):
return ('faq-topic-detail',(), {'slug': self.slug})
class Section(Audit):
name = models.CharField(max_length=255)
sort = models.SmallIntegerField()
slug = models.SlugField()
topic = models.ForeignKey(Topic,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "sections"
def __str__(self):
return self.name
def get_absolute_url(self):
return ('faq-section-detail',(), {'topic__slug': self.topic.slug,
'slug': self.slug})
class Article(Audit):
title = models.CharField(max_length=255)
sort = models.SmallIntegerField()
slug = models.SlugField()
section = models.ForeignKey(Section,on_delete=models.CASCADE)
answer = models.TextField()
vote_up = models.IntegerField()
vote_down = models.IntegerField()
view_count = models.IntegerField(default=0)
class Meta:
verbose_name_plural = "articles"
def __str__(self):
return self.title
def total_votes(self):
return self.vote_up + self.vote_down
def percent_yes(self):
return (float(self.vote_up) / self.total_votes()) * 100
def get_absolute_url(self):
return ('faq-article-detail',(), {'topic__slug': self.section.topic.slug,
'section__slug': self.section.slug, 'slug': self.slug})
Mysite Forms
class CreateArticleForm(forms.ModelForm):
class Meta:
model = Article
widgets = {
'answer': forms.Textarea(attrs={'data-provide': 'markdown', 'data-iconlibrary': 'fa'}),
}
fields = ('title','section','answer')
Mysite Views
class TopicCreateView(CreateView):
model = Topic
fields = ['name']
template_name = "faq/form_create.html"
success_url = "/ironfaq"
def form_valid(self, form):
topic = form.save(commit=False)
activity_user = self.request.user.username
activity_date = datetime.datetime.now()
topic.save()
return super(TopicCreateView,self).form_valid(form)
class SectionCreateView(CreateView):
model = Section
fields = ['name', 'topic']
template_name = "faq/form_create.html"
def form_valid(self, form):
section = form.save(commit=False)
activity_user = self.request.user.username
activity_date = datetime.datetime.now()
section.save()
self.success_url = "/ironfaq/%s/%s" % (section.topic.slug,section.slug)
return super(SectionCreateView,self).form_valid(form)
class ArticleCreateView(CreateView):
model = Article
form_class = CreateArticleForm
template_name = "faq/form_create.html"
def form_valid(self, form):
article = form.save(commit=False)
activity_user = self.request.user.username
activity_date = datetime.datetime.now()
article.save()
self.success_url = "/ironfaq/%s/%s/%s" % (article.section.topic.slug,article.section.slug,article.slug)
return super(ArticleCreateView,self).form_valid(form)
Let's say that you have this url
url(r'^ironfaq/(?P<topic_pk>\d+)/article/create$', ArticleCreateView.as_view(), name=’article-create’)
Where topic_pk will be pk of topic you want to be associated with your Article.
Then you just need to retrieve it in view. And this is done like this
class ArticleCreateView(CreateView):
model = Article
form_class = CreateArticleForm
template_name = "faq/form_create.html"
def form_valid(self, form):
article = form.save(commit=False)
# what are this variables for?
activity_user = self.request.user.username
activity_date = datetime.datetime.now()
# here we are getting 'topic_pk' from self.kwargs
article.topic_id = self.kwargs['topic_pk']
article.save()
self.success_url = "/ironfaq/%s/%s/%s" % (article.section.topic.slug,article.section.slug,article.slug)
return super(ArticleCreateView,self).form_valid(form)
All url params are stored in self.args and self.kwargs. Our topic_pk is named parameter and thats why we can get it by doing self.kwargs['topic_pk']
But be sure to validate existence of Topic with such pk before assigning it to your Article