Django Endpoint to accept string, boolean, and list

I want to build an endpoint that takes 3 parameters, either in JSON or as parameters.

One of them should be a string, one a boolean (or a string of true/false) and one is a list of ids that I will use in a custom SQL query to modify the database for an admin function I have to do sometimes.

All of this goes outside the “standard” Django way - as this data is not an Object or a Model, it is a list of ID’s from one model that will be used in an “IN()” clause in my custom SQL statement.

The closest I have come is something like this. But it doesn’t take 3 parameters (nor am I sure how to finesse the urls.py file in my inherited app to accomplish this)

Any advice appreciated. I’m new to Django and find that any time I want to do anything “out of the ordinary” and NOT use serializers etc, it’s a real wrestling match.

I used GenericViewSet to get away from all the Django magic since I just wanted this one endpoint to do one thing… I’d probably do the same with the one I want to build - that I’m asking about here - but I don’t know how to get it to take a list of ids…

Note that I got the one parameter this way: url_path=r’(?P<state_code>[^/.]+)’

Is there a way to add two more parameters? One for the boolean/string and one for the list? How would Django know that it’s supposed to be a list?
Is there a better way? Perhaps a way to make swagger show a space to put a list into the Swagger UI the way some of the endpoints allow JSON?
I found this…

@action(detail=False, url_path=r'table_meta/(?P<catalog>[^/.]+)/(?P<schema>[^/.]+)/(?P<table>[^/.]+)')

but that still doesn’t tell me if or how I can make one of them a list…

TIA!

class AgentLicenseBooleanViewSet(viewsets.GenericViewSet):
“”“Agent license boolean REST view
“””
# lookup_field is all we need in this case (unlike the other viewset classes) as we’re only building a
# custom endpoint with a custom query that goes outside the standard Django model/serializer/queryset patttern.
# This is how you get Django to stop forcing the “default” primary key for the model or a plain “id” in Swagger.
# This shows up on Swagger in addition to the two parameters declared on the url_path line below
# This parameter will show up on EVERY swagger endpoint created using methods in this class (see agents/urls.py)
lookup_field = ‘agent_id’

@action(methods=["GET"], detail=True,
        # causes the parameters listed to show up on swagger and be passed into the **kwargs
        # uses "named group syntax" from regex as well as a regex.
        # And no, I couldn't find any way to get Django to show the id fields as ints on Swagger without having to
        # import a whole new swagger-related library which didn't seem worth it.
        url_path=r'(?P<state_code>[^/.]+)',
        url_name='has_license_in_state')
def has_license_in_state(self, *_args, **kwargs) -> Response:
    try:
        agent_id = int(kwargs['agent_id'])
        state_code = kwargs['state_code']

        query = """
                    SELECT EXISTS (
                    SELECT id FROM agent_license
                    WHERE agent_id = %s
                    AND state_code = %s
                    ) as result;
                    """

        with connection.cursor() as cursor:
            # According to https://www.stackhawk.com/blog/sql-injection-prevention-django/
            # This is how to prevent SQL Injection on a "raw" query into the database using external input.
            # Django "sanitizes" the variables passed in the array on the 2nd parameter (array)
            # in the cursor.execute(string, array) statement -- in order, using the %s "entries" in the query string.
            # Obviously, order of operation matters here.
            cursor.execute(query, [agent_id, state_code])
            # row contains a tuple of (1, ) for true or (0, ) for false
            # depending on if the query returned 1 or 0 (true or false)
            row = cursor.fetchone()
            # get the value (1 or 0) at the first location in the tuple (the true or false)
            index = row[0]

        # we can use index (row[0]) as an index into the array [False, True] and it will always return the right thing.
        return Response(data=[False, True][index])

    except Exception as e:
        return Response(
            data={'Error': f'Unexpected error! Error is: [ {e} ]',
                  'agent_id': f'Agent id submitted was {agent_id}',
                  'state_id': f'State code submitted was {state_code}',
                  }, status=500)

That is not an accurate statement or characterization.

If it’s JSON, you access the submitted data in the request.body object. If they’re url parameters, you can access them from request.GET.

Once you have the data submitted, you can still validate those parameters using a Django form - there’s actually nothing within the form code that requires submitted data be presented as HTTP post data.

It might help if you were to post what you expect the actual submission to be, we can then guide you as to how you can best reference it.

Side note: If you want to submit three values as url parameters, see the examples at URL dispatcher | Django documentation | Django.

As an example, I want to submit:

“both” and “true” and (1234, 4384, 9876,8765,…)

Then I will get that data out of the request object and use it in my query as I did in the example above.

As which format? You’ve mentioned a couple different possibilities but didn’t identify which one you’re intending to use.

(Given what you’ve provided as a sample, JSON would probably be the easiest, but that’s just a guess at the moment.)

It’s not important which format. JSON is fine. Parameters on the URL are fine.

Let’s say JSON as that will be more straightforward for users I think - especially with a list involved.

I appreciate the link - and I’ve messed with URL stuff in this app I’ve inherited and frankly can’t tell what is going on. Things that are declared do not show up in Swagger… things are declared in several different ways… Again, being new to Django, it’s all pretty confusing… I don’t really grok how the URLs files can be helpful in getting the right thing into an endpoint call - or - I almost do, but the examples I’ve tried I haven’t been able to make work in my inherited app - for some reason.

I’m an old Java guy and inherited a mature Django app recently…

Reading a bit now…

I’ve tried stuff like this in my inherited app thinking it looked super easy and straightforward

path(“articles/int:year/int:month/”, views.month_archive),

But it never shows up when I run the app - so something must be wrong or there’s some magic that’s excluding it from Swagger or something…

First a side note: When you’re posting single lines of code (or a fragment of a line) surround that fragment with single backtick - ` characters. Example from your post:
Without backticks - path(“articles/int:year/int:month/”, views.month_archive),
With - path("articles/<int:year>/<int:month>/", views.month_archive),

Second side note: What shows up (or doesn’t) in Swagger has never concerned me. I only care that the application responds to the url correctly, and that is handled by the url dispatcher by what’s defined in that path function call.

In this specific case: articles/<int:year>/<int:month>/, issuing an http get to the url “articles/2023/10/” will result in Django calling the view named month_archive in the views module. (Generally, “views.py”)

The view would then have a definition like def month_archive(request, year, month):. It’s then up to your view to handle those parameters appropriately.

I’ll point out here that using URL parameters will make one thing easier here, and that’s being able to avoid CSRF issues when posting data. (This might not be an issue if this is a mature app that already addresses this.)

If you want to pass the list of integers as a url parameter, you’ll want to pass that as a string, and parse/validate it within the view.

Well, I can always hit it with Postman. There must be some magic whereby some things show and some do not, although my users are used to going to the swagger page for their needs…

Yes, I understand that if I want it as a parameter, I’d probably have to pass it as a string.

Can you help me understand how to write the code for the endpoint that would take JSON? Not how to parse the data obviously - just how to set up the method so it’ll take JSON?

I’m not totally sure that a path statement like that will work on my app, but I haven’t ever tried to hit anything except via Swagger, so it may just work.

There’s nothing you need to “do”.

Whatever data is being posted in the request is available from request.body.

I see. So I’m getting wrapped around getting it to work in a Swagger context I guess.

OK, I’ll do a little experimenting. Thank you!

I built this in my views.py:

class IrisMatching(viewsets.GenericViewSet):
parser_classes = (parsers.JSONParser,)
serializer_class = # serializers.IrisLookupSerializer

# def get(self, request, *args, **kwargs):
#     return Response(request.data)

def post(self, request, *args, **kwargs):
    print(request.data)
    print(request.data)

I added this to my urls.py:

router.register('zTest', views.IrisMatching, basename='test')

I then ran show_urls

And nothing like zTest showed up in the results.

I assume that if it’s not in the results of show_urls, it’s not there…

I have no idea what’s wrong, but it’s not showing up…
Any advice appreciated.

First, for code that you post, enclose the code between lines of three backtick - ` characters. That means you’ll have a line of ```, then your code, then another line of ```. This forces the code to remain properly formatted and you don’t have to try to do stuff like prefixing each line with >.

Unfortunately, I know very little about DRF - hopefully one of the DRF-knowledgeable people here are following along and can answer.

Thanks much.

I found a configuration of the right base class in the view and the correct incantation in the urls file and it now works, sans any nice Swagger UI of course. That’s a huge step forward for me, gratzi!

I’ve got some questions about what the heck is going on with my URLs (and I suppose, DRF) but I’ll post another appropriately titled question on that.