-
Notifications
You must be signed in to change notification settings - Fork 13
Description
When creating a Host via post to the hosts endpoint, we get a Location header telling us were the new host is, which is done manually and repeatedly:
Lines 349 to 408 in d45d257
| def post(self, request, *args, **kwargs): | |
| if "name" in request.data: | |
| if self.queryset.filter(name=request.data["name"]).exists(): | |
| content = {'ERROR': 'name already in use'} | |
| return Response(content, status=status.HTTP_409_CONFLICT) | |
| if "ipaddress" in request.data and "network" in request.data: | |
| content = {'ERROR': '\'ipaddress\' and \'network\' is mutually exclusive'} | |
| return Response(content, status=status.HTTP_400_BAD_REQUEST) | |
| # request.data is immutable | |
| hostdata = request.data.copy() | |
| if 'network' in hostdata: | |
| try: | |
| ipaddress.ip_network(hostdata['network']) | |
| except ValueError as error: | |
| content = {'ERROR': str(error)} | |
| return Response(content, status=status.HTTP_400_BAD_REQUEST) | |
| network = Network.objects.filter(network=hostdata['network']).first() | |
| if not network: | |
| content = {'ERROR': 'no such network'} | |
| return Response(content, status=status.HTTP_404_NOT_FOUND) | |
| ip = network.get_random_unused() | |
| if not ip: | |
| content = {'ERROR': 'no available IP in network'} | |
| return Response(content, status=status.HTTP_404_NOT_FOUND) | |
| hostdata['ipaddress'] = ip | |
| del hostdata['network'] | |
| if 'ipaddress' in hostdata: | |
| ipkey = hostdata['ipaddress'] | |
| del hostdata['ipaddress'] | |
| host = Host() | |
| hostserializer = HostSerializer(host, data=hostdata) | |
| if hostserializer.is_valid(raise_exception=True): | |
| with transaction.atomic(): | |
| # XXX: must fix. perform_creates failes as it has no ipaddress and the | |
| # the permissions fails for most users. Maybe a nested serializer should fix it? | |
| # self.perform_create(hostserializer) | |
| hostserializer.save() | |
| self.save_log_create(hostserializer) | |
| ipdata = {'host': host.pk, 'ipaddress': ipkey} | |
| ip = Ipaddress() | |
| ipserializer = IpaddressSerializer(ip, data=ipdata) | |
| if ipserializer.is_valid(raise_exception=True): | |
| self.perform_create(ipserializer) | |
| location = request.path + host.name | |
| return Response(status=status.HTTP_201_CREATED, headers={'Location': location}) | |
| else: | |
| host = Host() | |
| hostserializer = HostSerializer(host, data=hostdata) | |
| if hostserializer.is_valid(raise_exception=True): | |
| self.perform_create(hostserializer) | |
| location = request.path + host.name | |
| return Response(status=status.HTTP_201_CREATED, headers={'Location': location}) |
Now, we have a MregListCreateAPIView implements Location for create...
But for most list views, this class is not inherited. As one of many examples, see IPaddressList
which inherits from HostPermissionsListCreateAPIView which inherits from HostLogMixin and MregPermissionsListCreateAPIView... But none of these classes implement Location headers for post. So, using post on the ipaddress endpoint does not return a location header for the newly created entry...
Now, there may be a reason for this. We have models that look like this:
class Ipaddress(BaseModel):
host = models.ForeignKey(
Host, on_delete=models.CASCADE, db_column="host", related_name="ipaddresses"
)
ipaddress = models.GenericIPAddressField()
macaddress = models.CharField(
max_length=17, blank=True, validators=[validate_mac_address]
)
class Meta:
db_table = "ipaddress"
unique_together = (("host", "ipaddress"),)So how would a location header look like? Sadly, the best we can do is /api/v1/ipaddresses/?host=<host>&ipaddress=<ipaddress>.
This is 1) inconsistent, 2) very annoying when trying to write automation, 3) missing a unique ID field that would resolve all this.
Addendum: There is an autogenerated id / pk field. We just don't return it in post. :(