I want to upload multiple images.
class IssuePanel(models.Model):
issue = models.ForeignKey(ComicIssue, on_delete=models.CASCADE)
panel = models.FileField(upload_to='comic_issues_files/panels/')
date_uploaded = models.DateTimeField(auto_now_add=True)
After following the examples on django-multiupload's repository on github, I have this on forms.py
class PanelsForm(forms.ModelForm):
class Meta:
model = ComicIssue
fields = ('issue', 'issue_title', 'issue_cover', 'issue_description', 'issue_cover', 'issue_file')
panels = MultiFileField(min_num=1, max_num=20, max_file_size=2048*2048*5)
def save(self, commit=False):
instance = super(PanelsForm, self).save()
for each in self.cleaned_data['panels']:
IssuePanel.objects.create(panel=each, issue=instance)
return instance
views.py
class ComicIssueCreate(LoginRequiredMixin, CreateView):
model = ComicIssue
slug_field = 'comicseries_id'
form_class = PanelsForm
def form_valid(self, form):
obj = form.save(commit=False)
obj.title = ComicSeries.objects.get(id=self.kwargs['pk'])
obj.user = self.request.user
obj.save()
return redirect('comics:series_detail', pk=obj.title.id, slug=obj.title.slug)
urls.py
url(r'^comic/issue/(?P<pk>[0-9]+)/add/$', views.ComicIssueCreate.as_view(), name='comic-issue-add'),
However, I get this error
IntegrityError at /comic/issue/21/add/
NOT NULL constraint failed: comics_comicissue.title_id
class ComicIssue(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE,
null=True, blank=True, verbose_name='Uploaded by: '
)
title = models.ForeignKey(ComicSeries, on_delete=models.CASCADE, verbose_name='Series Title')
issue = models.CharField(verbose_name='Issue Number', max_length=500)
issue_title = models.CharField(verbose_name='Issue Title', max_length=1000)
issue_cover = models.ImageField(verbose_name='Issue cover', upload_to='comic_issues', height_field=None, width_field=None, max_length=None)
issue_description = models.TextField(verbose_name='Description')
issue_file = models.FileField(verbose_name='Issue file', upload_to='comic_issues_files', max_length=100,
help_text='File in pdf or as single image', null=True, blank=True
)
date_added = models.DateTimeField(auto_now_add=True, null=True)
is_favorite = models.BooleanField(default=False)
issue_slug = models.SlugField(default='')
class Meta:
verbose_name = 'Comic Issue'
verbose_name_plural = 'Comic Issues'
def __str__(self):
return '{}: {} issue number - {}'.format(self.title.title, self.issue_title, self.issue)
def save(self, *args, **kwargs):
self.issue_slug = slugify(self.issue_title)
super(ComicIssue, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('comics:issue_detail', kwargs={'issue_slug':self.issue_slug,'pk': self.pk})
Could this function in the ComicIssue model be a problem since it is also highlighted on the error page:
def save(self, commit=False, *args, **kwargs):
self.issue_slug = slugify(self.issue_title)
super(ComicIssue, self).save(*args, **kwargs)
I am passing the title_id from the url. It is working on other models just not this one. How do I save the foreign key?
You're saving your form in the form's save method regardless of the commit value. The first line is instance = super(PanelsForm, self).save() which will try to save a ComicIssue instance even though in your view, you wrote obj = form.save(commit=False).
You can do two things: pass the title and user to your form at init so the form can handle assigning those during save. Or change your form's save method to:
def save(self, commit=False):
instance = super(PanelsForm, self).save(commit=commit)
if commit:
for each in self.cleaned_data['panels']:
IssuePanel.objects.create(panel=each, issue=instance)
return instance
And then your view needs to call the form's save() method twice (note that the form's instance is passed by reference, so changing it in the view also changes the form's instance):
def form_valid(self, form):
obj = form.save(commit=False)
obj.title = ComicSeries.objects.get(id=self.kwargs['pk'])
obj.user = self.request.user
form.save()
return redirect('comics:series_detail', pk=obj.title.id, slug=obj.title.slug)
Related
I have a form to upload article posts but how would I filter the author so it just shows users who have the is_staff role, I also need it to show authors as usernames rather than email adresses
Here is my forms.py
class ArticleForm(forms.ModelForm):
tags = TagField(required=False, widget=LabelWidget)
class Meta:
model = Article
fields = ('article_name', 'author', 'content', 'tags', 'status')
Here is my views.py
class ArticleCreateView(LoginRequiredMixin, CreateView):
model = Article
form_class = ArticleForm
template_name = 'main_website/new_article.html' # <app>/<model>_<viewtype>.html
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
def get_context_data(self, **kwargs):
articles = Article.objects.filter(status=1).order_by('-date_posted')[:2]
context = super().get_context_data(**kwargs)
context['articles'] = articles
context['title'] = 'New Article'
return context
Here is my models.py
class Article(models.Model):
class Status(models.IntegerChoices):
Draft = 0
Published = 1
article_name = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='news_updates')
updated_on = models.DateTimeField(auto_now=True)
content = models.TextField()
tags = TaggableManager()
date_posted = models.DateTimeField(default=timezone.now)
status = models.IntegerField(choices=Status.choices, default=Status.Draft, help_text=_('Decide whether you want to Publish the news article or save it as a Draft'))
class Meta:
ordering = ['-date_posted']
def __str__(self):
return self.article_name
def get_absolute_url(self):
return reverse("main_website_article_detail", args=[str(self.slug)])
def save(self, *args, **kwargs):
self.slug = slugify(self.article_name)
super(Article, self).save(*args, **kwargs)
class ArticleForm(forms.ModelForm):
tags = TagField(required=False, widget=LabelWidget)
author = forms.ModelChoiceField(queryset=User.objects.filter(is_staff=True))
class Meta:
model = Article
fields = ('article_name', 'author', 'content', 'tags', 'status')
And for your second question (taken from django docs):
The str() method of the model will be called to generate string
representations of the objects for use in the field’s choices. To
provide customized representations, subclass ModelChoiceField and
override label_from_instance. This method will receive a model object
and should return a string suitable for representing it. For example:
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
hi i am working on a django app. functionality that i am implementing is to let my user buy a internet pack from the website.
i have implemented the model, view, template and url so far. but in the form i am getting a drop down list of all the users registered on the app. i automatically want django to link the user with current logged in user and let him select the pack he wants to buy and populate the model(table) automatically.
My models.py
def get_deadline():
return dt.today() + timedelta(days=30)
class CustomUser(AbstractUser):
Address = models.CharField(max_length=500)
def __str__(self):
return self.username
class Plans(models.Model):
plan_name = models.CharField(max_length=50)
speed = models.IntegerField()
price = models.FloatField()
def __str__(self):
return self.plan_name
class Orders(models.Model):
user = models.ForeignKey(CustomUser, on_delete = models.CASCADE)
pack = models.ForeignKey(Plans, on_delete = models.CASCADE)
start_date = models.DateField(auto_now_add=True)
end_date = models.DateField(default=get_deadline())
is_active = models.BooleanField(default=True)
def __str__(self):
name = str(self.user.username)
return name
my views.py
class UserBuyPlan(LoginRequiredMixin, View):
template = 'plans/plan.html'
#success_url = reverse_lazy('autos:all')
success_url = reverse_lazy('home-home')
def get(self, request):
form = BuyPlanForm()
ctx = {'form': form}
return render(request, self.template, ctx)
def post(self, request):
form = BuyPlanForm(request.CustomUser,request.POST)
if not form.is_valid():
ctx = {'form': form}
return render(request, self.template, ctx)
make = form.save()
return redirect(self.success_url)
my forms.py (i tried searching online and found this init implementation but it doesnt work)
class BuyPlanForm(forms.ModelForm):
class Meta():
model = Orders
fields = "__all__"
def __init__(self, *args, **kwargs):
self.user = CustomUser
super(BuyPlanForm, self).__init__(*args, *kwargs)
self.fields['user'].initial = self.user
the photo of resulting form is attached below
ok so i found the answer.
just had to change my view function a bit.
if you want a detailed tutorial then please visit https://www.youtube.com/watch?v=-s7e_Fy6NRU&t=1840s
he explains in a much better way.
class UserBuyPlan(LoginRequiredMixin, CreateView):
model = Orders
template_name = 'plans/plan.html'
fields = ['pack']
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
and also had to add absolute url method to my orders model
class Orders(models.Model):
user = models.ForeignKey(CustomUser, on_delete = models.CASCADE)
pack = models.ForeignKey(Plans, on_delete = models.CASCADE)
start_date = models.DateField(auto_now_add=True)
end_date = models.DateField(default=get_deadline())
is_active = models.BooleanField(default=True)
def __str__(self):
name = str(self.user.username)
return name
def get_absolute_url(self):
return reverse('home-home')
rest everything is same.
I've been trying to set up a basic private messaging system in Django using the generic CreateView.
I am currently having trouble with the "Receiver"/"To" field in my form. I tried to make it so it was a drop down field with the options being followers of the logged-in user.
Currently, the field is populating with the correct usernames (in this case, "testuser1") but it is throwing an error saying this field needs to be populated with an instance of the User object.
ValueError: Cannot assign "'testuser1'": "Message.reciever" must be a "User" instance.
Is there a way to have the form pass in the object of the username that is selected?
Model:
class Message(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sender")
reciever = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reciever")
subject = models.CharField(max_length=128, default="-")
content = models.TextField()
send_date = models.DateTimeField(default=timezone.now)
User Relationships Model:
class UserRelationships(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
created = models.DateTimeField(auto_now_add=True)
UPDATED Form:
class MessageCreateForm(forms.ModelForm):
class Meta:
model = Message
fields = ['sender', 'reciever', 'subject', 'content']
widgets = {'sender': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
follower_objects = kwargs.pop('follower_objects')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'] = RecieverModelChoiceField(queryset=User.objects.filter(username__in=follower_objects))
View:
class MessageCreateView(LoginRequiredMixin, CreateView):
model = Message
template_name = 'message/compose.html'
form_class = MessageCreateForm
def get_initial(self):
initial = super().get_initial()
initial['sender'] = self.request.user
return initial
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
user = self.request.user
followers = user.followers.values_list('user_id', flat=True)
follower_objects = []
kwargs['user'] = self.request.user
kwargs['follower_objects'] = follower_objects
for id in followers:
follower = User.objects.get(id=id)
follower_objects.append(follower)
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
You have to use forms.ModelChoiceField instead of forms.ChoiceField
ForeignKey (model) > ModelChoiceField (form) - Default widget: Select
ModelChoiceField has attribute queryset.
You can filter field reciever.queryset directly in MessageCreateForm.__init__ method.
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'].queryset = user.followers
UPDATE:
You can set a custom ModelChoiceField that will return any label you want (more info).
from django.forms import ModelChoiceField
class RecieverModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.username
or
def __init__(self, *args, **kwargs):
....
self.fields['reciever'].label_from_instance = lambda obj: "%s" % obj.username
I have a post model and try to achieve properly views counter but this method not work correctly in template when I try to display views counter in a template.
class Post(models.Model):
category = models.ForeignKey(Category, on_delete=models.PROTECT)
title = models.CharField(max_length=160)
slug = models.SlugField(null=True, blank=True, max_length=160)
content = models.TextField()
created = models.DateTimeField(default=datetime.datetime.now, blank=True)
updated = models.DateTimeField(auto_now=True)
photo = models.ImageField(upload_to='news/photos/', default='', blank=True)
views = models.PositiveIntegerField(default=0)
class Meta:
verbose_name = 'Post'
verbose_name_plural = 'News'
ordering = ['id']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
return super(Post, self).save(*args, **kwargs)
def count_views(self):
self.views = F('views') + 1
self.save()
class PostDetail(DetailView):
model = Post
template_name = 'news/detail.html'
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data(**kwargs)
self.object.count_views()
context['latest_news'] = Post.objects.all().order_by('-created')[0:10]
return context
<span><i class="icon-eye icons"></i> {{ post.views }}</span>
There may be another method to solve this issue, more correct. Can i solve this with .update queryset?
The issue is that you using an F expression to increment the views counter, but the object has to be reloaded in order for it to pick up the new value. You can use refresh_from_db() for this.
def count_views(self):
self.views = F('views') + 1
self.save()
self.refresh_from_db()
Without reloading, the value of self.views will be the string representation of the F() expression, which is what you're currently seeing in the template.
I have two models (OK 3 models since AssignedAsset is a subclass of Asset), one that tracks assets and another that tracks the history of owners for that asset. When I create a new asset using CreatView I would like to automatically have it create a History record as well.
models.py
class Asset(models.Model):
make = models.CharField(max_length=100)
model = models.CharField(max_length=100)
serial_number = models.CharField(max_length=100)
po = models.ForeignKey('purchaseorders.PurchaseOrder', default=None, blank=True, null=True)
location = models.ForeignKey('locations.Plant')
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.make}-{0.model}-{0.serial_number}".format(self)
self.slug = slugify(forslug)
super(Asset, self).save()
class AssignedAsset(Asset):
user = models.ForeignKey(User)
def __str__(self):
return self.slug
class AssignedHistory(models.Model):
assset = models.ForeignKey('Asset')
user = models.ForeignKey(User)
date = models.DateField()
slug = models.SlugField(blank=True, unique=True)
def __str__(self):
return self.slug
def save(self):
forslug = "{0.asset}-{0.date}".format(self)
self.slug = slugify(forslug)
super(AssignedHistory, self).save()
Here is my view.
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
success_url = '/assets'
And my forms.py
class AssignedAssetForm(forms.ModelForm):
class Meta:
model = AssignedAsset
fields = ['make', 'model', 'serial_number', 'location', 'user', 'po']
def __init__(self, *args, **kwargs):
super(AssignedAssetForm, self).__init__(*args, **kwargs)
#Filter out PO's that have packingslips (otherwise you will quickly have a ridicously big drop-down of every PO in the system)
self.fields['po'] = forms.ModelChoiceField(required=False, queryset=PurchaseOrder.objects.filter(packing_slip=''))
I thought maybe I could have it create the history when it gets the success URL, so I tried this in my view:
import time
def today():
return time.strftime ("%m/%d/%Y")
class NewAssignedAsset(CreateView):
form_class = AssignedAssetForm
template_name = 'createassignedasset.html'
def get_success_url(self):
history = AssignedHistory.objects.create(assset=self.object, user=self.object.user, date=today())
return '/assets'
But this throws a TypeError:
save() got an unexpected keyword argument 'force_insert'
Anything that would point me in the right direction would be appreciated.
You can do it at multiple levels(DB level, form level).
In your case, I'll say you just need to override the save() of your AssignedAssetForm. (Assuming you set user in context of form)
def save(self, *args, **kwargs):
assigned_asset = super(AssignedAssetForm, self).save(*args, **kwargs)
user = self.context.get(u'user')
if user:
assigned_asset_history = AssignedHistory(asset=assigned_asset, user=user, date=datetime.date.today())
assigned_asset_history.save()
return assigned_asset
** I am not sure about the context part, you may have to look into how to use user in form.
You should write your Asset.save() and AssignedHistory.save() as:
def save(self, **kwargs):
...
super(YourModel, self).save(**kwargs)
...
Note the **kwargs. They allow you to accept optional parameters (and a Model.save() has a few).