I'm a python/django newbie. Sorry for my english I'm not a native english speaker. I am trying to make a form where users can add in related models. On the admin site I am able to do this however I am having a hard time implementing the same thing on the website end.
Below is the admin screenshot. I want to implement on the front end the same thing where there's a way to add New Artist and New Tags to the form.
http://postimg.org/image/lv83s9fq5/
Here's my models.py
from django.db import models
from uuslug import uuslug
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
class Meta:
ordering = ["name"]
def __str__(self):
return self.slug
class Artist(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.CharField(max_length=100)
class Meta:
ordering = ["name"]
def __str__(self):
return self.name
class Song(models.Model):
S_KEYS = (
('C', 'C'),
('C#', 'C#'),
('D', 'D'),
('D#', 'D#'),
('E', 'E'),
('F', 'F'),
('F#', 'F#'),
('G', 'G'),
('G#', 'G#'),
('A', 'A'),
('A#', 'A#'),
('B', 'B'),
)
title = models.CharField(max_length=200)
artist = models.ForeignKey(Artist)
user = models.ForeignKey(User)
song_key = models.CharField(max_length=2, choices=S_KEYS)
body = models.TextField()
views = models.IntegerField(default=0)
slug = models.CharField(max_length=100, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
tags = models.ManyToManyField(Tag, blank=True)
class Meta:
ordering = ["title"]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = uuslug(self.title, instance=self, max_length=100)
super(Song, self).save(*args, **kwargs)
def add_view_count(self):
if self.views is not None:
self.views +=1
Here's what's in my views.py
class SongAdd(generic.CreateView):
template_name= 'song/add.html'
model = Song
form_class = SongForm
success_url = '/'
def form_valid(self, form):
form.instance.user = self.request.user
return super(SongAdd, self).form_valid(form)
I hope you can help me :)
Thanks in advance!
Use django-dynamic-formset app, the same that admin use with the name inlines.js. It has good examples for exactly what you need.
Related
So I have fairly many relations in models and now I'm trying to optimize db queries. One of the models i'm serializing is Thread model:
class Thread(models.Model):
participants = models.ManyToManyField(User, related_name='threads')
is_multi_participants = models.BooleanField(default=False)
creator = models.ForeignKey(User, verbose_name=_('creator'), on_delete=models.CASCADE)
last_sender = models.ForeignKey(User, verbose_name=_('last sender'), related_name='last_sender', on_delete=models.CASCADE)
prevent_reply = models.BooleanField(default=False, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=1000, blank=True, null=True)
company = models.ForeignKey(to=Company, verbose_name=_('Company'),
on_delete=models.SET_NULL, null=True, blank=True)
class ThreadListSerializer(serializers.ModelSerializer):
subtitle = serializers.SerializerMethodField()
class Meta:
model = Thread
fields = ('id',
'subtitle',
)
def get_subtitle(self, obj):
print(traceback.extract_stack(None, 2)[0][2], f' {len(connection.queries)}')
return ''
class ThreadList(generics.ListAPIView):
pagination_class = ThreadPagination
def get_queryset(self):
**queryset filtering goes here**
return queryset
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = ThreadListSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
Both view and serializer is simplified to debug one particular problem: while running this code in Pycharm (didn't matter in Debug or Run options) and making request from shell via curl, there are multiple calls of get_subtitle() function, thus resulting in many prints of to_representation *queries_number* to console. So the question is: is this a desired behavior? Should serializer be called as many times as queryset length?
I am trying to implement a feature to my backend and allow the owner of private "Group" to add other users by their usernames instead of ID's and allow them to add their images to FileField only once after they were added to the model. The code I have so far:
models.py
class Group(models.Model):
group_name = models.CharField(max_length=255)
group_text = models.TextField(max_length=360, blank=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='owner_user', on_delete=models.SET(get_deleted_user), default=1)
created_on = models.DateTimeField(auto_now_add=True, null=True)
shared_to = models.ManyToManyField(UserProfile, blank=True, related_name='shared_to_user', null=True)
def __str__(self):
return self.group_name
def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs)
class GroupImage(models.Model):
group_file = models.FileField(blank=True, null=True,
upload_to='media/covers/%Y/%m/%D/')
gallery_group = models.ForeignKey(Group, related_name='images', on_delete=models.CASCADE)
serializers.py
class GroupImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.GroupImage
fields = ('group_file', )
class SharedToSerializer(serializers.ModelSerializer):
class Meta:
model = models.Group
fields = ('shared_to', )
class GroupSerializer(serializers.ModelSerializer):
images = GroupImageSerializer(many=True, read_only=True)
person = SharedToSerializer(many=True, read_only=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'group_text', 'person', 'images')
def create(self, validated_data):
images_data = self.context.get('view').request.FILES
owner_id = self.context['request'].user.id
gallery_group = models.Group.objects.create(group_name=validated_data.get('group_name', 'no-
group_name'), group_text=validated_data.get('group_text'), owner_id=1)
for image_data in images_data.values():
models.GroupImage.objects.create(gallery_group=gallery_group,
group_file=image_data)
return gallery_group
views.py
class GroupCreateAPIView(generics.CreateAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupSerializer
permission_classes = [AllowAny]
So if your only requirement is how to add users by their username and not their id. You should use SlugRelatedField. I also feel your serializer naming convention is quite confusing. Below is the serializer for Group model that can add users to a group.
class GroupSerializer(Serializer):
... other fields here
shared_to = models.SlugRelatedField(queryset = UserProfile.objects.all(), many=True, slug_field="username", allow_empty=True)
So first checkout SlugRelatedField. This basically is used to map to objects using a specific field of that object(username in this case). You will then get all the UserProfile instances in the shared_to field of the validated_data
property of the serializer which you can fetch in create method and add to you group. And then in the file upload api for your group you can check whether this user belongs to the group or not for permission checking.
I want to return some useful information after POST to my api. I have
# views.py
from .serializers import FlagInstanceSerializer
SomeViewSet(viewsets.ModelViewSet):
...
#action(detail=True, methods=['post'])
def flag(self, request, pk=None):
some_model = self.get_object()
flag_instance = flaggit.utils.flag(some_model, user=request.user, ip=None, comment=None)
serializer = FlagInstanceSerializer(data=flag_instance)
serializer.is_valid()
return Response(data=serializer.data)
model
# models.py
class FlagInstance(models.Model):
flag = models.ForeignKey(Flag, related_name='flags', on_delete=models.CASCADE)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
ip = models.GenericIPAddressField(blank=True, null=True)
datetime = models.DateTimeField(auto_now_add=True)
flag_type = models.PositiveIntegerField(choices=FLAG_TYPES, default=1)
comment = models.TextField(blank=True, null=True)
def __str__(self):
return u'%s: %s' % (self.user, self.flag.content_object)
serializer
# serializers.py
from flaggit.models import FlagInstance
class FlagInstanceSerializer(serializers.ModelSerializer):
class Meta:
model = FlagInstance
fields = '__all__'
The rest docs show the following sanity check.
In [1]: from polls.serializers import FlagInstanceSerializer
In [2]: serializer = FlagInstanceSerializer()
In [3]: print(repr(serializer))
FlagInstanceSerializer():
id = IntegerField(label='ID', read_only=True)
ip = IPAddressField(allow_null=True, required=False)
datetime = DateTimeField(read_only=True)
flag_type = ChoiceField(choices=((1, 'Inappropriate'), (2, 'Move To Jobs'), (3, 'Move To Events'), (4, 'Move To Promotions')), required=False, validators=[<django.core.validators.MinValueValidator object>, <django.core.validators.MaxValueValidator object>])
comment = CharField(allow_blank=True, allow_null=True, required=False, style={'base_template': 'textarea.html'})
flag = PrimaryKeyRelatedField(queryset=Flag.objects.all())
user = PrimaryKeyRelatedField(allow_null=True, queryset=User.objects.all(), required=False)
The api responds with {}. I would like the api to respond with a json representation of a FlagInstance object.
You need to actually put an instance "into" the serializer to serialize the values into JSON. You are currently modifying a property that is used to deserialize the data into python objects.
Change your viewset method to this, replacing data with instance:
#action(detail=True, methods=['post'])
def flag(self, request, pk=None):
some_model = self.get_object()
flag_instance = flaggit.utils.flag(some_model, user=request.user, ip=None, comment=None)
serializer = FlagInstanceSerializer(instance=flag_instance)
return Response(data=serializer.data)
I'm using django-crispy-forms and would like to use autocomplete-light but can't get it going. I need users to be able to create a new facility if the one they want doesn't exist.
I just have no idea how to use autocomplete-light and I've been struggling for days. Can someone please point me in the right direction??
models.py
class CollectionFacility(TimeStampedModel):
"""
Data collection facility.
"""
facility_name = models.CharField(max_length=256, blank=False)
address_line1 = models.CharField("Address line 1", max_length=45)
address_line2 = models.CharField("Address line 2", max_length=45, blank=True)
country = models.CharField(max_length=50, blank=False)
state_province = models.CharField(max_length=100, blank=True)
city = models.CharField(max_length=100, blank=False)
postal_code = models.CharField("Postal Code", max_length=20, blank=True)
facility_contact = models.ForeignKey('FacilityContact', related_name='collection_facilities', null=True, blank=True)
def __unicode__(self):
return "%s, %s" % (self.facility_name, self.country)
class Meta:
ordering = ['country', 'facility_name', 'city', 'state_province']
verbose_name = "Collection Facility"
verbose_name_plural = "Collection Facilities"
class FacilityContact(TimeStampedModel):
TITLES = (
('Mrs.', 'Mrs.'),
('Ms.', 'Ms.'),
('Mr.', 'Mr.'),
('Dr.', 'Dr.'),
)
first_name = models.CharField(max_length=256, blank=False)
middle_initial = models.CharField(max_length=4, blank=True)
last_name = models.CharField(max_length=256, blank=False)
title = models.CharField(max_length=4, choices=TITLES, blank=True)
email = models.EmailField(blank=False)
def __unicode__(self):
return "%s, %s" % (self.last_name, self.first_name)
class Meta:
ordering = ['last_name', 'first_name']
verbose_name = "Facility Contact"
verbose_name_plural = "Facility Contacts"
forms.py
class FacilityForm(autocomplete_light.ModelForm):
class Meta:
model = CollectionFacility
views.py
facility_form = FacilityForm()
# pass it in the context to template
....
template.html
{% crispy facility_form %}
Did you check the non_admin_add_another example app ?
Docs about that one have not yet been ported to v2 which mean the code in the docs might not work. However note that autocomplete_light.example_apps.non_admin_add_another should work.
I recommend you start fiddling with that example directly in autocomplete_light's test_project, see: http://django-autocomplete-light.readthedocs.org/en/stable-2.x.x/demo.html
My models.py:
USER_TYPES = (
('D', 'Demo' ),
('F', 'Free' ),
('P', 'Premium'),
)
class BaseProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
user_type = models.CharField(max_length=1, blank=True, choices=USER_TYPES)
class DemoProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
demo = models.CharField(max_length=10, blank=True)
...
class FreeProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
free = models.CharField(max_length=10, blank=True)
...
class PremiumProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
premium = models.CharField(max_length=10, blank=True)
...
class ProxyProfile(BaseProfile):
class Meta:
proxy = True
def get_profile(self):
if self.user_type == 'D':
return DemoProfile._default_manager.get(user__id__exact=self.user_id)
elif self.user_type == 'F':
return FreeProfile._default_manager.get(user__id__exact=self.user_id)
else:
return PremiumProfile._default_manager.get(user__id__exact=self.user_id)
I use BaseProfile to map user_id to specific user_type. I wanted to use ProxyProfile as proxy which loads user_type depending profiles to ModelForm as shown below
Content of my forms.py:
class ProfileForm(ModelForm):
...
class Meta:
model = ProxyProfile
exclude = ('user','user_type')
...
ProfileForm is provided to django-profiles using following code in urls.py:
urlpatterns += patterns('',
url(r'^profiles/edit/', edit_profile,
{'form_class': ProfileForm},
name='profiles_edit_profile'),
(r'^profiles/',include('profiles.urls')),
)
I've also set in settings.py:
AUTH_PROFILE_MODULE = 'main.ProxyProfile'
During user registration all db data is filled correctly (it looks like everything is OK).
I register using form passed to django-registration:
urlpatterns += patterns('',
url(r'^register/$', register,
{'form_class': UserRegistrationForm},
name='registration.views.register'),
(r'', include('registration.urls')),
)
from forms.py:
class UserRegistrationForm(RegistrationFormUniqueEmail, RegistrationFormTermsOfService):
utype = forms.ChoiceField(choices=USER_CHOICES)
def save(self, profile_callback=None):
new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
password.self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
new_base_profile = BaseProfile(user=new_user, user_type=self.cleaned_data['utype'])
if self.cleaned_data['utype'] == "D":
new_profile = DemoProfile(user=new_user)
if self.cleaned_data['utype'] == "F":
new_profile = FreeProfile(user=new_user)
if self.cleaned_data['utype'] == "P":
new_profile = PremiumProfile(user=new_user)
new_profile.save()
new_base_profile.save()
return new_user
And registration phase works OK.
I've problem with profile edit/details pages.
My profiles filtered in ProxyProfile model and used as FormModel in ProfileForm
are not rendered (I can't see profile specific fields are not rendered to HTML page)
Maybe there is some other way (more like Django way :)) to do this
(select and render profile model depending on user_type field which is related to User model).
Thanks in advance :)
Ok, finally I've had an idea how I can do this :)
In my models.py:
class BaseManager(models.Manager):
def get(self, **kwargs):
self.u = kwargs['user__id__exact']
self.bt = BaseProfile.manager.get(user__id__exact=self.u)
if self.bt.user_type == 'F':
return FreeProfile.objects.get(pk=self.u)
elif self.bt.user_type == 'I':
return PremiumProfile.objects.get(pk=self.u)
else:
return None
class BaseProfile(models.Model):
objects = BaseManager()
manager = UserManager()
user = models.OneToOneField(User, primary_key=True)
user_type = models.CharField(max_length=1, blank=True, choices=USER_TYPES)
class FreeProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
free = models.CharField(max_length=10, blank=True)
...
class PremiumProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
premium = models.CharField(max_length=10, blank=True)
...
In custom manager - BaseManager I return profile object by overwriting get() method used by get_profile. I have to use UserManager named simply 'manager' to prevent recursive call of custom manager when assigning self.bt
OK, this is a half way to achive what I want, now I can view different profiles attached to users using django-profiles app.
Next, I want to use ModelForm to prepare edit form for user profiles. Users can have different profiles so I've applied the magic trick presented in this snippet: http://djangosnippets.org/snippets/2081/
And now in my forms.py I have:
class FreeForm(forms.ModelForm):
class Meta:
model = FreeProfile
class PremiumForm(forms.ModelForm):
class Meta:
model = PremiumProfile
next, simple model forms for each profile are assembled in ProfileForm:
class ProfileForm(ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs['instance'].user
profile_kwargs = kwargs.copy()
profile_kwargs['instance'] = self.user
self.bt = BaseProfile.manager.get(user__id__exact=self.user.id)
if self.bt.user_type == 'F':
self.profile_fields = FreeForm(*args, **profile_kwargs)
elif self.bt.user_type == 'P':
self.profile_fields = PremiumForm(*args, **profile_kwargs)
super(ProfileForm, self).__init__(*args, **kwargs)
self.fields.update(self.profile_fields.fields)
self.initial.update(self.profile_fields.initial)
class Meta:
model = BaseProfile
def save(self):
...
In settings.py:
AUTH_PROFILE_MODULE = 'main.BaseProfile'
And it works like a charm but I wonder if it is the Django way to achieve support for multiple different profiles using django-profiles?
It worries me that I have to use get() few more times before I render profile details or edit form.
But after 4 days of struggling with Django to get this done finally I can sleep well tonight :)
Cheers