I need to pass a parameter in the URL to the Django Admin add view, so that when I type the URL: http://localhost:8000/admin/myapp/car/add?brand_id=1, the add_view reads the brand_id parameter. I want to use this so that I can set a default value for the brand attribute of Car.
def get_form(self, request, obj=None, **kwargs):
form = super(CarAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['brand'].initial = <<GET['brand_id']>>
return form
Use case: I want this because in my BrandsAdmin, I have added a "+" button for each brand, that should add a Car with that Brand as FK.
I've tried getting it from request in get_form, but when that code is executed, Django has already changed my URL as I guess it doesn't understand it as a "legal" parameter.
Thanks a lot!
Using django 3 i did it overriding get_form in CarAdmin.py like you did, and its working for me, maybe the trick is in Brand.py:
Brand.py
readonly_fields = ('get_add_link',)
def get_add_link(self, obj):
url = f"{reverse('admin:myapp_car_add')}?brand={self.id}"
return format_html(f"<a href='{url}'><i class='fas fa-plus'></i></a>")
CarAdmin.py
def get_form(self, request, obj=None, **kwargs):
form = super(CarAdmin, self).get_form(request, obj, **kwargs)
brand_id = request.GET.get('brand')
if brand_id and len(brand_id) > 0:
form.base_fields['brand'].initial = brand_id
You can find a full explanation here, but shortly:
You can access query parameters directly from the view, the request object has a method. I suppose it is a GET request, so an example for your implementation would be:
request.GET.getlist("brand_id")
or
request.GET.get("brand_id")
Mainly, for what I wanted to do, it's as easy as two steps.
First, add a column in list_display to show the link to the add_view:
Brand.py
#admin.display(description='Link', ordering='url')
def get_add_link(self):
url = f"{reverse('admin:myapp_car_add')}?brand={self.id}"
return format_html(f"<a href='{url}'><i class='fas fa-plus'></i></a>")
Note that I add the URL as the reverse of admin:<app>_<model>_<action> and pass the brand ID as parameter.
CarAdmin.py
def get_changeform_initial_data(self, request):
brand_id = request.GET.get('brand')
return {'brand': brand_id}
By adding this method in the CarAdmin class, I am able to set the brand as the default value.
I did not have to do anything with the Model. The only change needed was in the ModelAdmin:
def get_changeform_initial_data(self, request):
brand = None
brand_id = request.GET.get('brand')
if (brand_id):
brand = Brand.objects.get(id=brand_id)
return {'brand': brand}
Docs link here: https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_changeform_initial_data
Related
I'm new to Django and I'm having a hard time understanding forms when the data to choose from are not taken from the database nor user input that they're generated on the go.
I currently have a template with a single ChoiceField. The data inside this field aren't fixed and they're calculated on the go once the page is requested. To calculate it I need the username of the User who is logged in. Basically, the calculation returns a list of lists in the form of ((title, id),(title,id),(title,id)), etc. that I need to put into the ChoiceField to make the User choose from one of the options.
Now, I'm not understanding how to pass the calculated list of lists to the form. I've tried to add the calculations inside the form as below but it is clearly the wrong way.
The main issue is that, to calculate my list of lists, I need the request value, and I don't know how to access it from the form.
Another idea was to add the generate_selection function inside the init but then I don't know how to pass main_playlist to being able to add it to ChoiceField
Below my not working forms.py
forms.py
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=HERE_SHOULD_GO_main_playlist)
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
main_playlist = []
for playlists in user_playlists["items"]:
playlists_list = []
playlists_list.append(playlists['name'])
playlists_list.append(playlists['id'])
main_playlist.append(playlists_list)
return main_playlist
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('playlists',)
The views should be something like below so I'm able to pass the request
views.py
form = ChoosePlaylistForm(request=request)
Maybe overriding the field choices in the form constructor would work:
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=())
class Meta:
model = User
fields = ('playlists',)
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['playlists'].choices = self.generate_selection()
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
choices = []
for playlist in user_playlists["items"]:
playlist_choice = (playlist["name"], playlist["id"])
choices.append(playlist_choice)
return choices
I want delete serializer's content to show.I am using Django Rest Framework.I am making a system return Json of serializers.Furthermore,I did not want to show user_id data. I wrote in views.py
class InfoViews(viewsets.ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def update(self,request, *args, **kwargs):
obj = UserInfo.objects.get(pk=kwargs['id'])
data = request.data
info_serializers = InfoSerializer(obj, data = data)
if info_serializers.is_valid(raise_exception=True):
info_serializers.save()
del info_serializers.data['user_id']
return JsonResponse(info_serializers.data)
Now all son data is shown.What is wrong in my code?How should I fix this?
You can specify in the serializers which fields you want to include or exclude in the response:
http://www.django-rest-framework.org/api-guide/serializers/#specifying-which-fields-to-include
look for : Specifying which fields to include
hope this helpes
First Clone the object into a new variable and then pop the key.
if info_serializers.is_valid(raise_exception=True):
info_serializers.save()
import copy
new = copy.deepcopy(info_serializers.data)
new.pop('user_id', None)
return JsonResponse(new)
Is the any solution to get django's user_full_name as a initial value for form? My idea was to display a django's form on the end of shopping to finish a order. I want also do put into a form total value, but this is for later.
I did something like this:
user_dane = request.user.get_full_name
koszyk = request.session.get('koszyk', [])
produkty = list(Produkt.objects.filter(pk__in=koszyk))
suma_cen = Produkt.objects.filter(pk__in=koszyk).aggregate(suma=Sum('cena'))
suma_wszystkich_cen = suma_cen['suma']
form=ZamowienieForm(initial={'imie_nazwisko':user_dane, 'kwota_do_zaplaty':suma_wszystkich_cen})
but this is working only when request.method is POST.
if request.method =='POST':
form = ZamowienieForm()
According to documentation I shouldn't initial a empty form with POST... Is there any chance to have a user full name into a form?
Here is the form class:
class ZamowienieForm(forms.ModelForm):
class Meta:
model = Zamowienie
fields = ('imie_nazwisko', 'kwota_do_zaplaty', 'miejscowosc',
'ulica','numer_domu', 'numer_mieszkania', 'kod_pocztowy',)
class NewMeta:
readonly = ('imie_nazwisko','kwota_do_zaplaty',)
Maybe try something like this inside ZamowienieForm class
def __init__(self, *args, **kwargs):
super(ZamowienieForm, self).__init__(*args, **kwargs)
self.fields['imie_nazwisko'] = self.initial.get('imie_nazwisko')
self.fields['kwota_do_zaplaty'] = self.initial.get('kwota_do_zaplaty')
Although I don't understand why "initial" is not working out of the box
In this case, you only need to initialize your form once, and not inside a conditional check if the request is a GET or POST:
def your_view(request):
form = ZamowienieForm(
request.POST or None,
initial={'imie_nazwisko': request.user.get_full_name(),
'kwota_do_zaplaty': suma_wszystkich_cen}
)
if request.method == 'POST' and form.is_valid():
# do whatever
This way you are always passing in the initial value, and if request.method == 'GET', then None is passed as the first positional argument to the form.
Also, user.get_full_name is an instance method, not a property, so using request.user.get_full_name only returns the bound method, not the actual value. You have have to call the function using ()
Finally, this will only work for users that are authenticated. The anonymous user object in Django won't return any user-specific information.
i am trying to call a class based view and i am able to do it, but for some reason i am not getting the context of the new class that i am calling
class ShowAppsView(LoginRequiredMixin, CurrentUserIdMixin, TemplateView):
template_name = "accounts/thing.html"
#method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(ShowAppsView, self).dispatch(*args, **kwargs)
def get(self, request, username, **kwargs):
u = get_object_or_404(User, pk=self.current_user_id(request))
if u.username == username:
cities_list=City.objects.filter(user_id__exact=self.current_user_id(request)).order_by('-kms')
allcategories = Category.objects.all()
allcities = City.objects.all()
rating_list = Rating.objects.filter(user=u)
totalMiles = 0
for city in cities_list:
totalMiles = totalMiles + city.kms
return self.render_to_response({'totalMiles': totalMiles , 'cities_list':cities_list,'rating_list':rating_list,'allcities' : allcities, 'allcategories':allcategories})
class ManageAppView(LoginRequiredMixin, CheckTokenMixin, CurrentUserIdMixin,TemplateView):
template_name = "accounts/thing.html"
def compute_context(self, request, username):
#some logic here
if u.username == username:
if request.GET.get('action') == 'delete':
#some logic here and then:
ShowAppsView.as_view()(request,username)
What am i doing wrong guys?
Instead of
ShowAppsView.as_view()(self.request)
I had to do this
return ShowAppsView.as_view()(self.request)
Things get more complicated when you start using multiple inheritance in python so you could easily be trampling your context with that from an inherited mixin.
You don't quite say which context you are getting and which one you want (you're not defining a new context), so it's difficult to completely diagnose, but try rearranging the order of your mixins;
class ShowAppsView(LoginRequiredMixin, CurrentUserIdMixin, TemplateView):
this implies that LoginRequiredMixin will be the first class to inherit from, and so it will take precedence over the others if it has the attribute you're looking for - if it hasn't then python will look in CurrentUserIdMixin and so on.
If you want to be really sure that you get the context that you're after, you could add an override like
def get_context(self, request):
super(<my desired context mixin>), self).get_context(request)
to ensure that the context you get is the one from the mixin that you want.
* Edit *
I don't know where you've found compute_context but it's not a django attribute so will only get called from ShowAppsView.get() and never in ManageAppView.
If there a way to detect if information in a model is being added or changed.
If there is can this information be used to exclude fields.
Some pseudocode to illustrate what I'm talking about.
class SubSectionAdmin(admin.ModelAdmin):
if something.change_or_add = 'change':
exclude = ('field',)
...
Thanks
orwellian's answer will make the whole SubSectionAdmin singleton change its exclude property.
A way to ensure that fields are excluded on a per-request basis is to do something like:
class SubSectionAdmin(admin.ModelAdmin):
# ...
def get_form(self, request, obj=None, **kwargs):
"""Override the get_form and extend the 'exclude' keyword arg"""
if obj:
kwargs.update({
'exclude': getattr(kwargs, 'exclude', tuple()) + ('field',),
})
return super(SubSectionAdmin, self).get_form(request, obj, **kwargs)
which will just inform the Form to exclude those extra fields.
Not sure how this will behave given a required field being excluded...
Setting self.exclude does as #steve-pike mentions, make the whole SubSectionAdmin singleton change its exclude property.
A singleton is a class that will reuse the same instance every time the class is instantiated, so an instance is only created on the first use of the constructor, and subsequent use of the constructor will return the same instance. See the wiki page for a more indept description.
This means that if you write code to exclude the field on change it will have the implication that if you first add an item, the field will be there, but if you open an item for change, the field will be excluded for your following visits to the add page.
The simplest way to achieve a per request behaviour, is to use get_fields and test on the obj argument, which is None if we are adding an object, and an instance of an object if we are changing an object. The get_fields method is available from Django 1.7.
class SubSectionAdmin(admin.ModelAdmin):
def get_fields(self, request, obj=None):
fields = super(SubSectionAdmin, self).get_fields(request, obj)
if obj: # obj will be None on the add page, and something on change pages
fields.remove('field')
return fields
Update:
Please note that get_fields may return a tuple, so you may need to convert fields into a list to remove elements.
You may also encounter an error if the field name you try to remove is not in the list. Therefore it may, in some cases where you have other factors that exclude fields, be better to build a set of excludes and remove using a list comprehension:
class SubSectionAdmin(admin.ModelAdmin):
def get_fields(self, request, obj=None):
fields = list(super(SubSectionAdmin, self).get_fields(request, obj))
exclude_set = set()
if obj: # obj will be None on the add page, and something on change pages
exclude_set.add('field')
return [f for f in fields if f not in exclude_set]
Alternatively you can also make a deepcopy of the result in the get_fieldsets method, which in other use cases may give you access to better context for excluding stuff. Most obviously this will be useful if you need to act on the fieldset name. Also, this is the only way to go if you actually use fieldsets since that will omit the call to get_fields.
from copy import deepcopy
class SubSectionAdmin(admin.ModelAdmin):
def get_fieldsets(self, request, obj=None):
"""Custom override to exclude fields"""
fieldsets = deepcopy(super(SubSectionAdmin, self).get_fieldsets(request, obj))
# Append excludes here instead of using self.exclude.
# When fieldsets are defined for the user admin, so self.exclude is ignored.
exclude = ()
if not request.user.is_superuser:
exclude += ('accepted_error_margin_alert', 'accepted_error_margin_warning')
# Iterate fieldsets
for fieldset in fieldsets:
fieldset_fields = fieldset[1]['fields']
# Remove excluded fields from the fieldset
for exclude_field in exclude:
if exclude_field in fieldset_fields:
fieldset_fields = tuple(field for field in fieldset_fields if field != exclude_field) # Filter
fieldset[1]['fields'] = fieldset_fields # Store new tuple
return fieldsets
class SubSectionAdmin(admin.ModelAdmin):
# ...
def change_view(self, request, object_id, extra_context=None):
self.exclude = ('field', )
return super(SubSectionAdmin, self).change_view(request, object_id, extra_context)
The approach below has the advantage of not overriding the object wide exclude property; instead it is reset based on each type of request
class SubSectionAdmin(admin.ModelAdmin):
add_exclude = ('field1', 'field2')
edit_exclude = ('field2',)
def add_view(self, *args, **kwargs):
self.exclude = getattr(self, 'add_exclude', ())
return super(SubSectionAdmin, self).add_view(*args, **kwargs)
def change_view(self, *args, **kwargs):
self.exclude = getattr(self, 'edit_exclude', ())
return super(SubSectionAdmin, self).change_view(*args, **kwargs)
I believe you can override get_fieldsets method of ModeAdmin class. See the example below, in the code example below, I only want to display country field in the form when adding a new country, In order to check if object is being added, we simply need to check if obj == None, I am specifying the fields I need. Now otherwise obj != None means existing object is being changed, so you can specify which fields you want to exclude from the change form.
def get_fieldsets(self, request: HttpRequest, obj=None):
fieldset = super().get_fieldsets(request, obj=obj)
if obj == None: # obj is None when you are adding new object.
fieldset[0][1]["fields"] = ["country"]
else:
fieldset[0][1]["fields"] = [
f.name
for f in self.model._meta.fields
if f.name not in ["id", "country"]
]
return fieldset
You can override the get_exclude method of the admin.ModelAdmin class:
def get_exclude(self, request, obj):
if "change" in request.path.split("/"):
return [
"fields",
"to",
"exclude",
]
return super().get_exclude(request, obj)
I think this is cleaner than the provided answers. It doesn't override the exclude field of the Class explicitly, but rather only contextually provides the fields you wish to exclude depending on what view you're on.