Python/Django Saving m2m with Through - python

UPDATE: I created a github repo with a full site demonstration of the problem.
Maybe my description below isn't quite communicating what I'm trying to do.
The github repo is: https://github.com/theCodeJerk/m2m-through
I really appreciate any help you may offer.
The code below is stripped down to illustrate the issue. While there are things that you may want to say "why would you do this anyway", there is probably a reason in the larger context :)
Here is my view:
class SubmissionCreate(CreateView):
model = Submission
fields = '__all__'
template_name_suffix = '_create_form'
success_url = '/'
Here is the relevant models.py code:
def custom_filename(instance, filename):
author = instance.publishers[0]
return 'papers/{0}.pdf'.format(author.pseudonum)
class Submission(models.Model):
name = models.CharField(
max_length=200,
blank=False
)
upload = models.FileField(
blank=True,
upload_to=custom_filename
)
publishers = models.ManyToManyField(
'Publisher',
blank=False,
related_name='publisher_of',
through='SubmissionPublisher'
)
class Publisher(models.Model):
user = models.ForeignKey(
User, blank=False,
on_delete=models.CASCADE
)
pseudonym = models.CharField(
max_length=200,
blank=False
)
class SubmissionPublisher(models.Model):
publisher = models.ForeignKey(
'Publisher',
blank=False,
on_delete=models.CASCADE
)
submission = models.ForeignKey(
'Submission',
blank=False,
on_delete=models.CASCADE
)
The problem is in the custom_filename, because I need the first publisher from the instance to generate the filename. The Submission is not yet saved when the SubmissionPublisher needs it to be saved.
What would the best way to do this be. Hopefully I have made sense here.
Thanks for any help!

Probably you can try like this:
First, update your custom_filename method:
def custom_filename(instance, filename):
if instance:
authors = instance.publishers.all()
if authors.exists():
author = authors[0]
return 'papers/{0}.pdf'.format(author.pseudonum)
return filename
Here I have fixed few issues, for example in your code instances.publishers[0] won't work, because you need to use a queryset method(like all(), or filter() etc) to access Publisher instances.
Then, make upload field nullable. Because you can't create M2M relations without creating Submission instance, and you can't create Submission instance with upload not null, because it requires an image.
class Submission(models.Model):
name = models.CharField(
max_length=200,
blank=False
)
upload = models.FileField(
null=True, default=None,
blank=True,
upload_to=custom_filename
)
Then, create a Form and override the save method:
from django import forms
from .models import Submission
class SubmissionForm(forms.ModelForm):
class Meta:
model = Submission
fields = '__all__'
def save(self, commit=True):
uploaded_file = self.cleaned_data.pop('upload')
instance = super().save(commit=True)
instance.upload = uploaded_file
instance.save()
return instance
Here I am pulling out the value for upload and saving the instance first. Then putting the image later. This code will work because upload field is nullable in your Submission model.
Finally, use that form class in your SubmissionCreate view:
class SubmissionCreate(CreateView):
model = Submission
form_class = SubmissionForm
template_name_suffix = '_create_form'
success_url = '/'

Related

Save to a model which is referencing a custom user model (via ForeignKey) in Django

I am trying to save to a model named Blog from inside Django's views.py file. This Blog model is itself linked to the custom user model that I created.
How exactly do I do that? Below are the
models.py file (custom user model is here)
models.py file (Blog model created here - in another Django app)
views.py file where I try to save to the Blog model. How do I reference the user here?
Please excuse the noob-ness of this question. I'm just starting out :)
Inside models.py, I have a custom user model:
class UserExtended(AbstractUser):
is_email_verified = models.BooleanField(default=False)
company = models.CharField(null=True, blank=True, max_length=255)
position = models.CharField(null=True, blank=True, max_length=255)
email = models.EmailField(unique=True)
I also created a model for blog articles in models.py:
class Blog(models.Model):
title = models.CharField(max_length=200)
blogSubject = models.CharField(null=True, blank=True, max_length=200)
keywords = models.CharField(null=True, blank=True, max_length=300)
audience = models.CharField(null=True, blank=True, max_length=200)
# connection to custom user model
profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
In views.py, I am trying to save to the Blog model:
def saveBlogTopic(request, blogTopic):
# create a Blog model instance
blog = Blog.objects.create(
title = blogTopic
blogSubject = request.session['blogSubject']
keywords = request.session['keywords']
audience = request.session['audience']
profile = request.user ### ???????? ###
)
I have no idea how to reference the custom user model when saving to the Blog model, which itself is linked via ForeignKey to the custom user model. See last line of code in views.py.

display objects from inline model in DetailView

I have 2 models Product and Resource. Resource has to be a TabularInline model in my admin panel. I am struggling with filtering resource titles that are related only to this product. Since it is a ForeignKey I should use select_related but I am not sure how to use it in my case. For now, the loop in my HTML file gives me all sales files (from all products).
models.py
class Product(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField('title', max_length=400, default='')
slug = models.SlugField(unique=True, blank=True, max_length=600)
class Resource(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=32, choices=RESOURCE_TYPE, default='sales')
title = models.CharField(max_length=400, blank=False, null=False, default='')
related_files = models.FileField(upload_to='recources/', null=True, blank=True)
publish = models.DateTimeField('Published on', default=timezone.now)
resources = models.ForeignKey(Product, default='', on_delete=models.PROTECT, null=True, related_name='resources')
admin.py
class Resourceinline(admin.TabularInline):
model = Resource
class ProductAdmin(ImportExportModelAdmin):
inlines = [
Resourceinline,
]
resource_class = ProductResource
admin.site.register(Product, ProductAdmin)
views.py
class ProductDetailView(DetailView):
template_name = 'ProductDetailView.html'
model = Product
def get_context_data(self, **kwargs):
context = super(ProductDetailView, self).get_context_data(**kwargs)
resources_sales = Resource.objects.select_related('resources').filter(resources_id =1, type='sales') # not sure what to put here
context['resources_sales'] = resources_sales
return context
ProductDetailView.html
{% for resource in resources_sales.all %}
<p>{{resource.title}}</p>
{% endfor %}
Question
Where am I making the mistake and how can I display resource objects that are related to type=sales and are related only to this product in DetailView.
Edit
I realized that there is a column named resources_id that is connecting both models. Now I am struggling to filter it by id of current DetailView. I put resources_id=1 in my views.py but it must relate to DetailView that user is currently looking at. I tied to put resources_id=self.kwargs['id'] but it gives me KeyError at /product/test-product/ 'id' How can I do that?
since you are using generic DetailView you can refer to the current object with self.get_object(). actually that return the single object that view display. however you can use instateself.object too.
so you can filter the Product related Resources using Resource.objects.filter(resources=self.get_object(), type='sales')
you can read more Single object mixins

Get site-specific user profile fields from user-created object

I am using Django sites framework (Django 2.1) to split an app into multiple sites. All of my models except the User model are site-specific. Here is my Post model:
post.py
class Post(models.Model):
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='children',
related_query_name='child',
blank=True,
null=True,
)
title = models.CharField(
max_length=255,
blank=True,
)
body_raw = models.TextField()
body_html = models.TextField(blank=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
I have no problem separating posts out by site. When I want to get the posts, I call:
posts = Post.on_site.filter(...)
I have a separate model called UserProfile. It is a many-to-one profile where there is a unique profile created for each user-site combination (similar to profile implementation at SE). The profile has a reputation attribute that I want to access when I get any post. This reputation attribute should be different for each site (like how on SE you have different rep on each site you are a member of).
user_profile.py
class UserProfile(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
reputation = models.PositiveIntegerField(default=1)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
How do I access the user's username (on the User model) as well as the user's reputation (on the UserProfile model) when I get Posts from a query?
I'd like to do something like:
Post.on_site.filter(...)
.select_related('user__userprofile')
.filter_related(user__userprofile.site == get_current_site())
How do I filter a Many-To-One related model?
Better to make UserProfile -> User relationship to be OnetoOne,
because Django doesn't know which of many profiles to show
(but you also need to define related_name)
models.OneToOneField(get_user_model(), related_name='userprofile_rev')
Then you will be able to do this
qs = Post.on_site.filer().select_related('user', 'user__userprofile_rev')
for post in qs:
print(post.user.username, post.user.userprofile_rev.reputation)
If you don't want to change your DB structure you can do like this
(but you need to specify which profile to return)
qs = Post.on_site.filer().select_related('user').prefetch_related('user__userprofile_set')
for post in qs:
print(post.user.username, post.user.userprofile_set[0].reputation)

How do I override the django admin LogEntry model

I want to change the model used for the default LogEntry class so it creates a varchar rather than a clob in the database for the attribute "object_id"
The original model is defined in
django/contrib/admin/models.py
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
I want to change the definition of object_id to
object_id = models.Charfield(_('object id'), max_length=1000, blank=True, null=True)
I'm aware that this could cause issues if someone defined a primary key on an entity which is larger than a varchar(1000), but I wouldn't want an entity with a PK defined like that so I'm happy with the limitation.
This will greatly improve the efficiency of the queries when accessing the history log.
I don't really want to hack the actual model definition, but I can't find out how to elegantly override the model definition.
Any ideas?
Django’s model fields provide an undocumented contribute_to_class method.
The other feature of Django we can use is the class_prepared signal.
from django.db.models import CharField
from django.db.models.signals import class_prepared
def add_field(sender, **kwargs):
"""
class_prepared signal handler that checks for the model named
MyModel as the sender, and adds a CharField
to it.
"""
if sender.__name__ == "MyModel":
field = CharField("New field", max_length=100)
field.contribute_to_class(sender, "new_field")
class_prepared.connect(add_field)
To override field you can simply delete original field from model:
from django.db.models import CharField
from django.db.models.signals import class_prepared
def override_field(sender, **kwargs):
if sender.__name__ == "LogEntry":
field = CharField('object id', max_length=1000, blank=True, null=True)
sender._meta.local_fields = [f for f in sender._meta.fields if f.name != "object_id"]
field.contribute_to_class(sender, "object_id")
class_prepared.connect(override_field)
I have just tested this solution by placing this code in __init__.py of my app. You will also need to write a custom migration:
from django.db import migrations, models
class Migration(migrations.Migration):
def __init__(self, name, app_label):
# overriding application operated upon
super(Migration, self).__init__(name, 'admin')
dependencies = [
('my_app', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='logentry',
name='object_id',
field=models.CharField('object id', max_length=1000, blank=True, null=True),
),
]
Looks like it works but use it on your own risk.
You can read more here.

Overriding formset in TabularInline Django admin form

I'm having trouble overriding the formset on a TabularInline inline of a ModelAdmin object in my admin site. I know you're supposed to have a model associated with a TabularInline object, but I'm not sure how to specify this on the form object used to generate the formset. With the code below, I'm getting "'AppAssetInline.formset' does not inherit from BaseModelFormSet."
class AppAssetForm(forms.ModelForm):
model = App.assets.through
primary = forms.BooleanField()
uuid = forms.CharField()
class AppAssetInline(admin.TabularInline):
model = App.assets.through
AssetFormset = formset_factory(AppAssetForm)
formset = AssetFormset
class AppAdmin(admin.ModelAdmin):
inlines = [AppAssetInline,]
The answer to my question didn't have to do with how I was structuring my forms, but rather how I was joining fields on my models. I had the following structure in my models:
class App(models.Model):
package = models.FileField(upload_to=settings.APP_PACKAGE_ROOT)
assets = models.ManyToManyField('AppAsset', blank=True, null=True)
download_count = models.IntegerField(default=0)
class AppAsset(models.Model):
def __unicode__(self):
return self.asset_file.name
notes = models.CharField(max_length=255, null=True, blank=True)
type = models.CharField(max_length=255, null=True, blank=True)
asset_file = models.FileField(upload_to=settings.APP_PACKAGE_ROOT)
What I did was change the structure such that AppAsset now has a foreign key on App for its assets. After that, I could use the TabularInline on the AppAsset model with no problems. Here are the latest source files:
https://github.com/ridecharge/spout/blob/master/Spout/AppDistribution/models.py
https://github.com/ridecharge/spout/blob/master/Spout/AppDistribution/admin.py
You should use django.forms.models.inlineformset_factory instead of formset_factory

Categories

Resources