crucialfelix / django-ajax-selects

jQuery UI-powered auto-complete fields for ForeignKey, ManyToMany and text fields
Other
822 stars 249 forks source link

Should +add popup work in the view outside of Admin? #283

Closed breduin closed 3 years ago

breduin commented 3 years ago

Hi! In Admin everything works perfectly: autocomplete, +add button, popup window with form for new object and so on.

Is DAS designed to do all the same outside of Admin?

I've written simple test project for Books with ForeignKey to Authors. The autocomplete outside of Admin works ok, the problem is to add new related object: the form for Book is opening in current window, not in popup, after form submission new Author is saved but not assigned to created Book. When one clicks the 'add' button one can see for a moment in the console the "Uncought ReferenceError: showAddAnotherPopup is not defined", then this message disappears and forms loaded. After form submission we have "_Uncaught TypeError: Cannot read property 'dismissAddRelatedObjectPopup' of null at popupresponse.js:13" So it seems that Django doesn't see for some reason popup handlers from RelatedObjectLookups.js .

Django 3.1.1 and 3.1.3, django-ajax-selects 2.0.0. Which Django versions have you tested for admin-outside features?

models.py

class Author(models.Model):
    name = models.CharField(max_length=32)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    name = models.CharField(max_length=32)

views.py

class BookCreateView(CreateView):
    model = Book
    template_name = 'book_form.html'
    success_url = '/admin/'

    def get_form_class(self):
        autoselect_fields_check_can_add(BookCreateForm, self.model, self.request.user)
        return BookCreateForm

forms.py

class BookCreateForm(forms.ModelForm):
    author = AutoCompleteSelectField('authors', required=True, help_text='Start typing with J', label='Author')

    class Meta:
        model = Book
        fields = '__all__'

lookups.py

@register('authors')
class AuthorsLookup(LookupChannel):

    model = Author
    min_length = 1 # Minimum number of characters user types before a search is initiated.

# Queryset matching the characters typed by the user
    def get_query(self, q, request):
        qs = self.model.objects.filter(name__startswith=q)[:10]
        return qs

book_form.html

<head>
    <meta charset="UTF-8">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.js"></script>
    {{ form.media }}
    <title>Title</title>
</head>
<body>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Save">
</form>
</body>

Here is the test project https://github.com/breduin/DAStest

crucialfelix commented 3 years ago

Hi. I have not tested it myself with Django 3. However if it works inside the admin then it works with that version. Then again you are seeing missing javascript errors.

Can you check it in the admin and see what extra files are loaded by form.media and where those callback functions are?

Maybe they refactored the media in 3x

You read this I'm sure:

https://django-ajax-selects.readthedocs.io/en/latest/Outside-of-Admin.html

breduin commented 3 years ago

Great idea! I've checked the lists of files loaded inside Admin and outside Admin by {{ form.media }}. They. indeed, differ.

{{ form.media }} loads the next:

<script src="/static/admin/js/jquery.init.js"></script>
<script src="/static/ajax_select/js/bootstrap.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript" src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="/static/ajax_select/js/ajax_select.js"></script>

Minimal list of scripts to get DAS working is:

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<script src="/static/ajax_select/js/bootstrap.js"></script>
<script src="/static/admin/js/admin/RelatedObjectLookups.js"></script>
<script src="/static/ajax_select/js/ajax_select.js"></script>

So that one needs additional load popup window handlers.

Still there is a point. The order of scripts is important. If I write this in the form template:

    {{ form.media }}

    <script src="/static/admin/js/admin/RelatedObjectLookups.js"></script>

so that _ajaxselect.js is imported before RelatedObjectLookups.js, then DAS works, but the block with selected item under input field is not displayed. To display it _ajaxselect.js must be loaded after RelatedObjectLookups.js, but one can't put RelatedObjectLookups.js before {{ form.media }}, since jQuery and jQuery-ui are not yet defined and DAS doesn't work. Finally, keeping {{ form.media }} in template I found this construction to be working:

   <script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
   <script type="text/javascript" src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
   <script src="/static/admin/js/jquery.init.js"></script>

   <script src="/static/admin/js/admin/RelatedObjectLookups.js"></script>

    {{ form.media }}

There is still JS error "jquery.init.js:8 Uncaught TypeError: Cannot read property 'noConflict' of undefined at VM5081 jquery.init.js:8", probably because jquery.init.js is loaded twice.

Resuming, {{ form.media }} needs to be rearranged. I've edited _media() function in fields.py, please, take a look for https://github.com/breduin/django-ajax-selects/tree/popup_media/ajax_select.