Recently I downloaded the Pinax project 0.7 to see what I can apply to my own project. Particularly, I ran Pinax and looked at the Bookmarks app that Pinax applied to itself. I copied the Bookmark apps and its dependencies to my own. However, Pinax runs on Django 1.0.4 and mine was 1.2.4 and there are some error in the form validation. Following are snippets from the bookmarks app:
class BookmarkInstance(models.Model):
bookmark = models.ForeignKey(Bookmark, related_name="saved_instances", verbose_name=_('bookmark'))
user = models.ForeignKey(User, related_name="saved_bookmarks", verbose_name=_('user'))
saved = models.DateTimeField(_('saved'), default=datetime.now)
description = models.CharField(_('description'), max_length=100)
note = models.TextField(_('note'), blank=True)
tags = TagField()
Form:
class BookmarkInstanceForm(forms.ModelForm):
url = forms.URLField(label = "URL", verify_exists=True, widget=forms.TextInput(attrs={"size": 40}))
description = forms.CharField(max_length=100, widget=forms.TextInput(attrs={"size": 40}))
redirect = forms.BooleanField(label="Redirect", required=False)
tags = TagField(label="Tags", required=False)
def __init__(self, user=None, *args, **kwargs):
self.user = user
super(BookmarkInstanceForm, self).__init__(*args, **kwargs)
# hack to order fields
self.fields.keyOrder = ['url', 'description', 'note', 'tags', 'redirect']
def clean(self):
if 'url' not in self.cleaned_data:
return
if BookmarkInstance.objects.filter(bookmark__url=self.cleaned_data['url'], user=self.user).count() > 0:
raise forms.ValidationError(_("You have already bookmarked this link."))
return self.cleaned_data
def save(self, commit=True):
self.instance.url = self.cleaned_data['url']
return super(BookmarkInstanceForm, self).save(commit)
class Meta:
model = BookmarkInstance
The params passed in were (<QueryDict: {u'url': [u'amazon.com'], u'note': [u'foo'], u'description': [u'bar'], u'tags': [u'']}>,) but did not cause a model validation error in 1.0.4. So, how can I make minimal adjustments in my view, possibly to skip the model validation to accommodate for this difference?
Related
I am currently trying to improve my form by restricting choices inside an input (user can choose only their own tags).
tag name / user name
I tried to do that inside the get/post function :
def post(self, request, *args, **kwargs):
form = DateInputForm(request.POST, limit_choices_to={'tags__user': request.user})
def get(self, request, *args, **kwargs):
form = DateInputForm(limit_choices_to={'tags__user': request.user})
(1) I get an error.
BaseModelForm.init() got an unexpected keyword argument 'limit_choices_to'
My form :
class DateInputForm(ModelForm):
class Meta:
model = Task
# exclude = ('user',)
fields = ['user', 'title', 'description', 'date_to_do', 'complete', 'tags']
widgets = {
'date_to_do': forms.DateTimeInput(format='%Y-%m-%dT%H:%M',
attrs={'type': 'datetime-local', 'class': 'timepicker'}),
}
My view :
class TaskUpdate(LoginRequiredMixin, UpdateView):
model = Task
template_name = "tasks/task_form.html"
form_class = DateInputForm
Tag model :
class Tag(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
tag_name = models.CharField(max_length=200, null=True, blank=False, default='')
Globally: the goal is to limit the tags that can be chosen by the user in my task form (with the tag input); currently, a user can choose another user's tag, which is not what I want.
I think the easiest way to do this is to override the constructor of your form, as shown in this answer: https://stackoverflow.com/a/1969081/18728725
Update:
Here is Wamz's solution:
def __init__(self, *args, **kwargs):
super(DateInputForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
new_kwargs = kwargs.get('instance')
self.fields['tags'].queryset = Tag.objects.filter(user=new_kwargs.user.id)
Warning, that's not working in the createView
I am building a notification system for a company, where admin users can create Projects and add users to them. The Project model has 9 attributes but I only want to show 3 or 4 fields when a Project is created, but show them all when an existing Project is updated.
This change will only need to be reflected on the Django admin site, so I have extended the ProjectAdmin with my own ProjectForm, where I extend the init method to check if it is a new instance and if so remove certain fields.
# models.py
class Project(models.Model):
project_number = models.IntegerField()
name = models.CharField(max_length=100)
permit = models.CharField(max_length=100, blank=True, default='')
is_active = models.BooleanField(default=True)
users = models.ManyToManyField(CustomUser, blank=True, related_name='project_users')
# add a default
levels = models.ManyToManyField('Level', blank=True, related_name='project_levels')
total_contract_hours = models.IntegerField(default=0, blank=True, verbose_name='Total Design Hours')
hours_used = models.IntegerField(default=0, blank=True, verbose_name='Total Design Hours Used')
notes = models.ManyToManyField('notes.ProjectNote', related_name='core_project_notes', blank=True)
history = HistoricalRecords()
def __str__(self):
ret_str = "{} {}".format(self.project_number, self.name)
if self.permit:
ret_str += " | Permit: {}".format(self.permit)
return ret_str
# admin.py
class ProjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
attrs = {'class': 'form-control', 'required': True}
if self.instance and self.instance.pk is None:
# creating project
exclude = ['is_active', 'users', 'levels', 'hours_used', 'notes']
for field in exclude:
try:
del self.fields[field]
except ValueError:
print('{} does not exist'.format(field))
for field in self.fields.values():
field.widget.attrs = attrs
class Meta:
model = Project
fields = ['project_number', 'name', 'total_contract_hours']
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
fields = ['project_number', 'name', 'permit', 'is_active', 'users', 'levels', 'total_contract_hours', 'hours_used', 'notes']
As I stated I only want basic Project fields on creation, but show all attributed when updating existing Project. With just these changes, I now get a KeyError:
KeyError: "Key 'is_active' not found in 'ProjectForm'. Choices are:
name, permit, project_number, total_contract_hours."
However, when I print the available fields it returns an OrderedDict with all of the model attributes as keys. What am I doing wrong? Thanks!
I figured it out, the field must be in listed in Meta and then you just set the field to be a hidden field.
class ProjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
print("Adding project")
if not self.instance or self.instance.pk is None:
for name, field in self.fields.items():
if name in ['design_manager', ]:
field.widget = forms.HiddenInput()
class Meta:
model = Project
fields = ['project_number', 'name', 'design_manager', 'total_contract_hours']
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
def save_model(self, request, obj, form, change):
obj.design_manager = request.user
super().save_model(request, obj, form, change)
I want to save the Portfolio products details in PortfolioProducts model in django
I have models like below:
class Product(models.Model):
name = models.CharField(max_length=255,null=True, verbose_name ='Name')
class Portfolio(models.Model):
name = models.CharField(max_length=100, blank=True, null=True, verbose_name ='Name')
class PortfolioProducts(models.Model):
portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, verbose_name ='Portfolio')
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name ='Product')
Portfolio form:
class PortfolioForm(forms.ModelForm):
class Meta:
model = Portfolio
fields = ['name']
My view file:
def edit(request):
portfolio_form = PortfolioForm
if request.method=="POST":
portfolio_id=request.POST.get('portfolio_id')
portfolio_detail = Portfolio.objects.get(pk=portfolio_id)
pform = portfolio_form(request.POST, instance=portfolio_detail)
if pform.is_valid():
portfolio = pform.save(commit = False)
portfolio.save()
products=request.POST.getlist('product_id[]')
for product in products:
ppform = PortfolioProducts(product_id=product, portfolio_id=portfolio_id)
port_product = ppform.save()
I am trying to save and update the Portfolio products like this, but is adding products to portfolio multiple time.
Well, you don't need to update PortfolioProduct for updating Portofilio. Because even if you update Portfolio, its primary key remains same as before. So the relationship remains the same.
But, in your case, if PortofolioProduct does not exist for a product in products and Portfolio object, then you can create one like this:
for product in products:
ppform, _ = PortfolioProducts.objects.get_or_create(product_id=product, portfolio_id=portfolio_id)
Update
From comments: you need to either remove def save(self): methods from you Model(Because you are not doing anything particular in those save methods) or if intend to keep you save() methods, then you need to call the super properly, like this:
class Product(models.Model):
name = models.CharField(max_length=255,null=True, verbose_name ='Name')
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
class Portfolio(models.Model):
name = models.CharField(max_length=100, blank=True, null=True, verbose_name ='Name')
def save(self, *args, **kwargs):
super(Portfolio, self).save(*args, **kwargs)
class PortfolioProducts(models.Model):
portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, verbose_name ='Portfolio')
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name ='Product')
def save(self, *args, **kwargs):
super(PortfolioProducts, self).save(*args, **kwargs)
Yes, I also got stuck with the same issue in my django project. The thing it does in my case was everytime the user tries to update his/her profile, it created a new one, this is because of the Foreign Key to it. I fixed the issue by deleting the previous user profile (in your case it's portfolio) every time the user updates it.
class UserEdit(TemplateView):
template_name = 'accounts/homee.html'
def get(self, request):
form = UserProfilee()
ppp = UserProfile.objects.get(user=request.user)
return render(request, self.template_name, {'form': form, 'ppp': ppp})
def post(self, request):
form = UserProfilee(request.POST, request.FILES)
pppp = UserProfile.objects.get(user=request.user)
if form.is_valid():
post = form.save(commit=False)
post.user = request.user
if not post.image:
post.image = pppp.image
UserProfile.objects.filter(user=post.user).delete()
post.save()
return redirect('/home/homepage/')
args = {'form': form}
return render(request, self.template_name, args)
As you see,I filter the user and delete the user profile whenever user updates his/her profile thus leaving only 1 user profile.
I can't work out how to get the correct instance for the form_valid part of my generic view.
I am trying to allow a user to post on their project wall(bit like Facebook). I need the post to be related to an individual project(a user can have more than one project). Should the instance be a pk or the project title? Any example code or help would be very appreciated! I struggle understanding how when you create a new post, it knows which project to associate itself with.
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
newpost = form.save(commit=False)
form.instance.user = self.request.user
newpost.save()
self.object = newpost
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
models
class UserProject(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
project_overview = models.CharField(max_length=1000)
project_picture = models.ImageField(upload_to='project_images', blank=True)
date_created = models.DateTimeField(auto_now_add=True)
project_views = models.IntegerField(default=0)
project_likes = models.IntegerField(default=0)
project_followers = models.IntegerField(default=0)
slug = models.SlugField(max_length=100, unique=True) #should this be unique or not?
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(UserProject, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
forms
#form to add project details
class UserProjectForm(forms.ModelForm):
class Meta:
model = UserProject
fields = ('title', 'project_picture', 'project_overview')
#form to create a post
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
Ok, in that case, I would recommend a URL something like
url(r'^(?P<pk>\d+)/post/add/$', views.NewPostCreateView.as_view(), name='...'),
and then a view like
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
# Find project by using the 'pk' in the URL
project = get_object_or_404(UserProject, pk=self.kwargs['pk'])
# Then just set the project on the newPost and save()
self.object.project = project
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
# Unchanged ...
I see in your code that you were trying to do something with the user but I don't understand why your Post does not have a user field (you may want to add a created_by) and the UserProject should already have a user set.
I am also assuming the user got to the his/her project first, so you know by definition that the project he is adding a post to is his. If that is not the case, then just change the logic to get the UserProject through a regular query. e.g. maybe with `UserProject.objects.get(user = self.request.user) if there is one project per user (again, just as an example).
Anyway, I am making some assumptions here, but hopefully the main question was how to set the project on the newPost and that is answered in my example.
I have some issue to solve with editing profile form of django-userena. I want to use EditProfileFormExtra (it is an extension of userena EditProfileForm) to edit related extra fields (choice fields) for Settings model, which are in OneToOne relation with Accounts aplication, a field: default_house_system.
Saving is ok, but initial value of default_house_system does not work, always display first value of tuple. I want do this without including any code in original files in Django-Userena.
Models:
class UserProfile(UserenaBaseProfile):
user = models.OneToOneField(User,
unique=True,
verbose_name='user',
related_name='user_profile')
settings = models.OneToOneField(Settings, unique=True, verbose_name="Ustawienia", blank=True, null=True)
is_virtual = models.BooleanField(default=False)
class Settings(models.Model):
points = models.IntegerField(default=0)
default_house_system = models.CharField(choices=HOUSE_SYSTEMS, max_length=24, default="Placidus")
Userena Form:
class EditProfileForm(forms.ModelForm):
""" Base form used for fields that are always required """
first_name = forms.CharField(label=_(u'First name'),
max_length=30,
required=False)
last_name = forms.CharField(label=_(u'Last name'),
max_length=30,
required=False)
def __init__(self, *args, **kw):
super(EditProfileForm, self).__init__(*args, **kw)
# Put the first and last name at the top
new_order = self.fields.keyOrder[:-2]
new_order.insert(0, 'first_name')
new_order.insert(1, 'last_name')
self.fields.keyOrder = new_order
class Meta:
model = get_profile_model()
exclude = ['user']
def save(self, force_insert=False, force_update=False, commit=True):
profile = super(EditProfileForm, self).save(commit=commit)
# Save first and last name
user = profile.user
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
I replace an urls definition, putting into accounts/urls.py:
url(r'^(?P<username>[\.\w]+)/edit/$',
'userena.views.profile_edit',
{'edit_profile_form': EditProfileFormExtra},
name='userena_profile_edit'),
... and here is definition of EditProfileFormExtra:
class EditProfileFormExtra(EditProfileForm):
default_house_system = forms.ChoiceField(choices=HOUSE_SYSTEMS)
def __init__(self, *args, **kw):
super(EditProfileFormExtra, self).__init__(*args, **kw)
new_order = self.fields.keyOrder[:-1]
new_order.insert(3, 'default_house_system')
self.fields.keyOrder = new_order
class Meta:
model = get_profile_model()
fields = ['mugshot', 'default_house_system']
def save(self, force_insert=False, force_update=False, commit=True):
profile = super(EditProfileFormExtra, self).save(commit=commit)
settings_profile = profile.settings
settings_profile.default_house_system = self.cleaned_data['default_house_system']
settings_profile.save()
profile.save()
return profile
Now IMPORTANT stuff. If I put:
'default_house_system': user.user_profile.settings.default_house_system
into dict in userena/views.py/profile_edit method (it is class-based view):
user_initial = {'first_name': user.first_name,
'last_name': user.last_name,
'default_house_system': user.user_profile.settings.default_house_system}
it is work fine! But I do not want to including any code in original files in Django-Userena.
Is there another way instead of copying whole profile_edit method from userena/views.py to my app accounts/views.py?
I would appreciate any tips or advices, thank you.