I have some models like that:
class BaseModel(models.Model):
created_by = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_created")
created_date = models.DateTimeField(_('Added date'), auto_now_add=True)
last_updated_by = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_updated")
last_updated_date = models.DateTimeField(_('Last update date'), auto_now=True)
class Meta:
abstract = True
class Image(BaseModel):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
name = models.CharField(_('Item name'), max_length=200, blank=True)
image = models.ImageField(_('Image'), upload_to=get_upload_path)
def save(self, *args, **kwargs):
if self.image and not GALLERY_ORIGINAL_IMAGESIZE == 0:
width, height = GALLERY_ORIGINAL_IMAGESIZE.split('x')
super(Image, self).save(*args, **kwargs)
filename = os.path.join( settings.MEDIA_ROOT, self.image.name )
image = PILImage.open(filename)
image.thumbnail((int(width), int(height)), PILImage.ANTIALIAS)
image.save(filename)
super(Image, self).save(*args, **kwargs)
class Album(BaseModel):
name = models.CharField(_('Album Name'), max_length=200)
description = models.TextField(_('Description'), blank=True)
slug = models.SlugField(_('Slug'), max_length=200, blank=True)
status = models.SmallIntegerField(_('Status'),choices=ALBUM_STATUSES)
images = generic.GenericRelation(Image)
I use BaseModel abstract model for my all models to track save and update logs. I can use ModelAdmin class to set user fields automatically:
class BaseAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.last_updated_by = request.user
obj.save()
class AlbumAdmin(BaseAdmin):
prepopulated_fields = {"slug": ("name",)}
list_display = ('id','name')
ordering = ('id',)
That works. All BaseAdmin fields are filled automatically. But I want to add Images to Albums by Inline. So, I change my admin.py like that:
from django.contrib.contenttypes import generic
class ImageInline(generic.GenericTabularInline):
model = Image
extra = 1
class AlbumAdmin(BaseAdmin):
prepopulated_fields = {"slug": ("name",)}
list_display = ('id','name')
ordering = ('id',)
inlines = [ImageInline,]
When I save page, I get an error: gallery_image.created_by_id may not be NULL on first super(Image, self).save(*args, **kwargs) row of Image model save method. I know it's because of GenericTabularInline class doesn't have a "save_model" method to override.
So, the question is, how can I override save method and set current user on InlineModelAdmin classes?
I have found a solution on another question: https://stackoverflow.com/a/3569038/198062
So, I changed my BaseAdmin model class like that, and it worked like a charm:
from models import BaseModel
class BaseAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.last_updated_by = request.user
obj.save()
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
if isinstance(instance, BaseModel): #Check if it is the correct type of inline
if not instance.created_by_id:
instance.created_by = request.user
instance.last_updated_by = request.user
instance.save()
Note that, you must extend same abstract class for the ModelAdmin that contains the inlines to use this solution. Or you can add that save_formset method to ModelAdmin that contains the inline specifically.
I wanted the user to be set on all my models no matter where/how they were manipulated. It took me forever to figure it out, but here's how to set it on any model using middleware:
"""Add user created_by and modified_by foreign key refs to any model automatically.
Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry
class WhodidMiddleware(object):
def process_request(self, request):
if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if hasattr(request, 'user') and request.user.is_authenticated():
user = request.user
else:
user = None
mark_whodid = curry(self.mark_whodid, user)
signals.pre_save.connect(mark_whodid, dispatch_uid = (self.__class__, request,), weak = False)
def process_response(self, request, response):
signals.pre_save.disconnect(dispatch_uid = (self.__class__, request,))
return response
def mark_whodid(self, user, sender, instance, **kwargs):
if instance.has_attr('created_by') and not instance.created_by:
instance.created_by = user
if instance.has_attr('modified_by'):
instance.modified_by = user
In addition to mindlace's answer; when the created_by field happens to have null=True the not instance.created_by gives an error. I use instance.created_by_id is None to avoid this.
(I'd rather have posted this as a comment to the answer, but my current reputation doesn't allow...)
Related
I have two models: one is the post model that can have multiple linked images. The image model has a foreign key to the post. That is, the images have a foreign key for a single post. When adding a new post I want to upload multiple images at the same time. Here is the code:
model.py
class Post(models.Model):
title = models.CharField(max_length=255)
thumbnail = models.ImageField(upload_to='thumbnails')
summary = RichTextField()
body = RichTextUploadingField()
created_at = models.DateField(auto_now_add=True)
class Meta:
ordering = ['-created_at',]
def __str__(self):
return self.title
class Imagens(models.Model):
img = models.ImageField(
upload_to = "media/",
)
post = models.ForeignKey(
"Post", on_delete=models.CASCADE, default=1)
admin.py
class ImageAdminForm(forms.ModelForm):
class Meta:
model = Imagens
fields = ['img',]
def __init__(self, *args, **kwargs):
super(ImageAdminForm, self).__init__(*args, **kwargs)
self.fields['img'] = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class ImageInline(admin.TabularInline):
model = Imagens
extra = 1
form = ImageAdminForm
class PostAdmin(admin.ModelAdmin):
search_fields = ['title']
inlines = [ImageInline,]
def save_model(self, request, obj, form, change):
obj.save()
files = request.FILES.getlist('img') #he gets nothing
print(files)
for f in files:
x = Imagens.objects.create(post=obj,img=f)
x.save()
admin.site.register(Post, PostAdmin)
The problem is that if I save the post object it saves only one image and if I try to get the list of images it gives an empty list. Sorry I'm new to Django and even python. Every help is welcome.
Well, that's just a workaround. What I did was make the main model share the form with the inlined model. So, on the change page the files appear and I'm not obliged to post the images either, but it's full of fields.
class ImageAdminForm(forms.ModelForm):
imgens = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
def __init__(self, *args, **kwargs):
super(ImageAdminForm, self).__init__(*args, **kwargs)
self.fields['imgens'].required = False
self.fields['img'] = forms.ImageField()
self.fields['img'].required = False
class ImageInline(admin.TabularInline):
model = Imagens
extra = 1
form = ImageAdminForm
class PostAdmin(admin.ModelAdmin):
search_fields = ['title']
inlines = [ImageInline]
form = ImageAdminForm
def save_model(self, request, obj, form, change):
obj.save()
files = request.FILES.getlist('imgens')
for f in files:
x = Imagens.objects.create(post=obj,img=f)
x.save()
admin.site.register(Post, PostAdmin)
My task is to change the value of one field in the form (drop-down list with Foreignkey connection). I need to exclude the values of technology that the user already has.
I use CreateView and ModelForm.
forms.py
class SkillCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SkillCreateForm, self).__init__(*args, **kwargs)
employee_current_technology = Technology.objects.filter(??? --- How can I get editing user pk ????-----)
self.fields['technology'].queryset = Technology.objects.exclude(name__in=employee_current_technology)
I know that somehow I can get pk from url using kwarg and get_form_kwarg values, but I can't figure out how to do that.
urls.py
path('profile/<int:pk>/skill/create/', SkillCreateView.as_view(), name='skill_create'),
views.py
class SkillCreateView(AuthorizedMixin, CreateView):
"""
Create new course instances
"""
model = Skill
form_class = SkillCreateForm
template_name = 'employee_info_create.html'
def get_form_kwargs(self):
kwargs = super(SkillCreateView, self).get_form_kwargs()
Employee.objects.get(pk=self.kwargs['pk']) -->get me pk
????
return kwargs
.....
models.py
class Employee(models.Model):
"""Employee information."""
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='employee')
summary = models.TextField("summary", blank=True, default='')
skills = models.ManyToManyField(
Technology, through="Skill", verbose_name="skills", blank=True)
class Skill(models.Model):
"""Information about an employee's skills."""
employee = models.ForeignKey(
Employee, on_delete=models.CASCADE, related_name="employee_skills")
technology = models.ForeignKey(Technology, on_delete=models.CASCADE)
class Technology(models.Model):
"""Technologies."""
tech_set = models.ForeignKey(Skillset, on_delete=models.CASCADE, related_name="skillset")
name = models.CharField('technology name', max_length=32, unique=True)
group = models.ForeignKey(Techgroup, on_delete=models.CASCADE, related_name="group")
You can inject the pk in the form, like:
class SkillCreateView(AuthorizedMixin, CreateView):
"""
Create new course instances
"""
model = Skill
form_class = SkillCreateForm
template_name = 'employee_info_create.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(employee_pk=self.kwargs['pk'])
return kwargs
You can then update the queryset in the form like:
class SkillCreateForm(forms.ModelForm):
def __init__(self, *args, employee_pk=None, **kwargs):
super().__init__(*args, **kwargs)
if employee_pk is not None:
self.fields['technology'].queryset = Technology.objects.exclude(
skill__employee_id=employee_pk
)
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'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .
I have a model like this:
class News(models.Model):
is_activity = models.BooleanField(default=False)
activity_name = models.CharField(max_length=240, blank=True, null=True)
What I am trying to achieve is, if is_activity is checked in I want activity_name to be required. Thus, I am trying to override the __init__ method:
class NewsForm(forms.ModelForm):
class Meta:
model = News
def __init__(self, *args, **kwargs):
super(NewsForm, self).__init__(*args, **kwargs)
if self.fields['is_activity'] is True:
self.fields['activity_name'].required = True
class NewsAdmin(FrontendEditableAdminMixin, admin.ModelAdmin):
form = NewsForm
Even if I check in the is_activity the activity_name is non-required. What's wrong?
The ModelForm.clean() method gives you access to the cleaned data – this is where you can include the field-specific conditional logic:
from django.core.validators import EMPTY_VALUES
class NewsForm(forms.ModelForm):
class Meta:
model = News
def clean(self):
is_activity = self.cleaned_data.get('is_activity', False)
if is_activity:
# validate the activity name
activity_name = self.cleaned_data.get('activity_name', None)
if activity_name in EMPTY_VALUES:
self._errors['activity_name'] = self.error_class([
'Activity message required here'])
return self.cleaned_data
class NewsAdmin(FrontendEditableAdminMixin, admin.ModelAdmin):
form = NewsForm