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)