initial commit
This commit is contained in:
		
							
								
								
									
										0
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										3
									
								
								admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								admin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | from django.contrib import admin | ||||||
|  |  | ||||||
|  | # Register your models here. | ||||||
							
								
								
									
										6
									
								
								apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | from django.apps import AppConfig | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AxcUlConfig(AppConfig): | ||||||
|  |     default_auto_field = 'django.db.models.BigAutoField' | ||||||
|  |     name = 'axc_ul' | ||||||
							
								
								
									
										110
									
								
								management/commands/load_survey.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								management/commands/load_survey.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | import numpy as np | ||||||
|  | from astropy.io import fits | ||||||
|  |  | ||||||
|  | from django.core.management.base import BaseCommand | ||||||
|  | from django.db import transaction | ||||||
|  | from axc_ul.models import Pixel | ||||||
|  |  | ||||||
|  | from itertools import islice | ||||||
|  |  | ||||||
|  | BATCH_SIZE = 1000000 | ||||||
|  |  | ||||||
|  | def batch(iterable, size): | ||||||
|  |     """ | ||||||
|  |     Generator that yields successive chunks of size 'size' from 'iterable'. | ||||||
|  |     """ | ||||||
|  |     iterable = iter(iterable) | ||||||
|  |     while True: | ||||||
|  |         chunk = list(islice(iterable, size)) | ||||||
|  |         if not chunk: | ||||||
|  |             break | ||||||
|  |         yield chunk | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Command(BaseCommand): | ||||||
|  |     help = "Process FITS files and store the data in the database" | ||||||
|  |  | ||||||
|  |     def add_arguments(self, parser): | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--counts',  | ||||||
|  |             type=str,  | ||||||
|  |             required=True, | ||||||
|  |             help='Path of the counts file' | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--exposure',  | ||||||
|  |             type=str,  | ||||||
|  |             required=True, | ||||||
|  |             help='Path of the exposure file' | ||||||
|  |         ) | ||||||
|  |         # parser.add_argument( | ||||||
|  |         #     '--survey_number',  | ||||||
|  |         #     type=int,  | ||||||
|  |         #     required=True, | ||||||
|  |         #     help='Integer ID of the survey being read' | ||||||
|  |         # ) | ||||||
|  |  | ||||||
|  |     def handle(self, *args, **options): | ||||||
|  |          | ||||||
|  |         counts_file = options['counts'] | ||||||
|  |         exposure_file = options['exposure'] | ||||||
|  |         # survey_number = options['survey_number'] | ||||||
|  |  | ||||||
|  |         self.stdout.write(f"Counts file:   {counts_file}") | ||||||
|  |         self.stdout.write(f"Exposure file: {exposure_file}") | ||||||
|  |  | ||||||
|  |         with fits.open(counts_file) as hdul: | ||||||
|  |  | ||||||
|  |             column_name = "T" | ||||||
|  |             counts_map = hdul[1].data[column_name] | ||||||
|  |  | ||||||
|  |         counts_data = counts_map.ravel() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         with fits.open(exposure_file) as hdul: | ||||||
|  |  | ||||||
|  |             column_name = "T" | ||||||
|  |             exposure_map = hdul[1].data[column_name] | ||||||
|  |  | ||||||
|  |         exposure_data = exposure_map.ravel() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         self.stdout.write(f"Counts Data Shape:   {counts_data.shape}") | ||||||
|  |         self.stdout.write(f"Exposure Data Shape: {exposure_data.shape}") | ||||||
|  |  | ||||||
|  |         assert counts_data.shape == exposure_data.shape, "Counts and exposure maps must have the same shape" | ||||||
|  |  | ||||||
|  |         #rate_data = np.divide(counts_data, exposure_data) | ||||||
|  |          | ||||||
|  |         # with transaction.atomic(): | ||||||
|  |              | ||||||
|  |         #     survey,created = Survey.objects.get_or_create(number=survey_number) | ||||||
|  |              | ||||||
|  |         #     if created: | ||||||
|  |         #         self.stdout.write(f"Created a new survey instance with number: {survey.number}") | ||||||
|  |         #     else: | ||||||
|  |         #         self.stdout.write(f"Using existing survey instance with the number: {survey.number}") | ||||||
|  |          | ||||||
|  |  | ||||||
|  |         # Create a generator that yields Pixel objects one by one. | ||||||
|  |         pixel_generator = ( | ||||||
|  |             Pixel( | ||||||
|  |                 hpid=i, | ||||||
|  |                 counts=int(count), | ||||||
|  |                 exposure=float(exposure), | ||||||
|  |                 #rate=float(rate), | ||||||
|  |                 #survey=survey | ||||||
|  |             ) | ||||||
|  |             for i, (count, exposure) in enumerate(zip(counts_data, exposure_data)) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         total_inserted = 0 | ||||||
|  |         # Process the generator in batches. | ||||||
|  |         for pixel_batch in batch(pixel_generator, BATCH_SIZE): | ||||||
|  |             with transaction.atomic(): | ||||||
|  |                 Pixel.objects.bulk_create(pixel_batch) | ||||||
|  |             total_inserted += len(pixel_batch) | ||||||
|  |             self.stdout.write(f"Inserted {total_inserted} pixels") | ||||||
|  |  | ||||||
|  |         self.stdout.write(f"Inserted a total of {total_inserted} pixels.") | ||||||
							
								
								
									
										35
									
								
								migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | # Generated by Django 5.1.6 on 2025-02-27 12:55 | ||||||
|  |  | ||||||
|  | import django.db.models.deletion | ||||||
|  | import uuid | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     initial = True | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Survey', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('number', models.IntegerField()), | ||||||
|  |             ], | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Pixel', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), | ||||||
|  |                 ('hpid', models.IntegerField()), | ||||||
|  |                 ('counts', models.IntegerField()), | ||||||
|  |                 ('exposure', models.FloatField()), | ||||||
|  |                 ('rate', models.FloatField()), | ||||||
|  |                 ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pixels', to='axc_ul.survey')), | ||||||
|  |             ], | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										22
									
								
								migrations/0002_remove_pixel_uuid_alter_pixel_id.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								migrations/0002_remove_pixel_uuid_alter_pixel_id.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | # Generated by Django 5.1.6 on 2025-02-27 19:35 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('axc_ul', '0001_initial'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='pixel', | ||||||
|  |             name='uuid', | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='pixel', | ||||||
|  |             name='id', | ||||||
|  |             field=models.AutoField(primary_key=True, serialize=False), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								migrations/0003_alter_pixel_hpid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								migrations/0003_alter_pixel_hpid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.1.6 on 2025-03-02 15:09 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('axc_ul', '0002_remove_pixel_uuid_alter_pixel_id'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='pixel', | ||||||
|  |             name='hpid', | ||||||
|  |             field=models.IntegerField(db_index=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								migrations/0004_survey_nside.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								migrations/0004_survey_nside.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.1.6 on 2025-03-02 16:37 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('axc_ul', '0003_alter_pixel_hpid'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='survey', | ||||||
|  |             name='nside', | ||||||
|  |             field=models.IntegerField(default=4096), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | # Generated by Django 5.1.6 on 2025-03-06 08:14 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('axc_ul', '0004_survey_nside'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='pixel', | ||||||
|  |             name='survey', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='pixel', | ||||||
|  |             name='rate', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Survey', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								migrations/0006_pixel_contaminated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								migrations/0006_pixel_contaminated.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 5.1.6 on 2025-03-13 13:27 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('axc_ul', '0005_remove_pixel_survey_remove_pixel_rate_delete_survey'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='pixel', | ||||||
|  |             name='contaminated', | ||||||
|  |             field=models.BooleanField(default=False), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										0
									
								
								migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										14
									
								
								models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | from django.db import models | ||||||
|  |  | ||||||
|  | class Pixel(models.Model): | ||||||
|  |      | ||||||
|  |     id = models.AutoField(primary_key=True) | ||||||
|  |      | ||||||
|  |     hpid = models.IntegerField(db_index=True) | ||||||
|  |      | ||||||
|  |     counts = models.IntegerField() | ||||||
|  |      | ||||||
|  |     exposure = models.FloatField() | ||||||
|  |      | ||||||
|  |     contaminated = models.BooleanField(default=False) | ||||||
|  |      | ||||||
							
								
								
									
										8
									
								
								serializers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								serializers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | # serializers.py | ||||||
|  | from rest_framework import serializers | ||||||
|  | from axc_ul.models import Pixel | ||||||
|  |  | ||||||
|  | class PixelSerializer(serializers.ModelSerializer): | ||||||
|  |     class Meta: | ||||||
|  |         model = Pixel | ||||||
|  |         fields = ('hpid', 'counts', 'exposure', 'contaminated') | ||||||
							
								
								
									
										3
									
								
								tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tests.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | from django.test import TestCase | ||||||
|  |  | ||||||
|  | # Create your tests here. | ||||||
							
								
								
									
										8
									
								
								urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								urls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | # urls.py | ||||||
|  | from django.urls import path | ||||||
|  | from .views import PixelDetailView, UpperLimitView | ||||||
|  |  | ||||||
|  | urlpatterns = [ | ||||||
|  |     path('pixel/<int:hpid>/', PixelDetailView.as_view(), name='pixel-detail'), | ||||||
|  |     path('upper-limit/', UpperLimitView.as_view(), name='upper-limit') | ||||||
|  | ] | ||||||
							
								
								
									
										157
									
								
								views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | |||||||
|  | # axc_ul/views.py | ||||||
|  |  | ||||||
|  | # from astropy_healpix import HEALPix does not have an option to | ||||||
|  | #                                     search for pixels non-inclusively | ||||||
|  | import healpy as hp | ||||||
|  | import astropy.units as u | ||||||
|  | from astropy.coordinates import SkyCoord | ||||||
|  |  | ||||||
|  | import scipy.special as sp | ||||||
|  |  | ||||||
|  | from rest_framework.views import APIView | ||||||
|  | from rest_framework.response import Response | ||||||
|  | from rest_framework import status | ||||||
|  |  | ||||||
|  | from django.shortcuts import get_object_or_404 | ||||||
|  |  | ||||||
|  | from axc_ul.models import Pixel | ||||||
|  |  | ||||||
|  | from .serializers import PixelSerializer | ||||||
|  |  | ||||||
|  | class PixelDetailView(APIView): | ||||||
|  |     """ | ||||||
|  |     API endpoint that returns the pixel data (counts, exposure, rate) | ||||||
|  |     for a given survey number and hpid. | ||||||
|  |     """ | ||||||
|  |     def get(self, request, hpid): | ||||||
|  |         # Get the survey using the survey_number field. | ||||||
|  |         # survey = get_object_or_404(Survey, number=survey_number) | ||||||
|  |          | ||||||
|  |         # Get the pixel corresponding to the survey and hpid. | ||||||
|  |         pixel = get_object_or_404(Pixel, hpid=hpid) | ||||||
|  |          | ||||||
|  |         # Serialize the pixel data to JSON. | ||||||
|  |         serializer = PixelSerializer(pixel) | ||||||
|  |         return Response(serializer.data, status=status.HTTP_200_OK) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UpperLimitView(APIView): | ||||||
|  |     """ | ||||||
|  |     Calculate and return upper limits (UL, CRUL, FLUL) based on provided RA, DEC, and CL parameters. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         request: HTTP GET request containing 'ra', 'dec', and 'cl' query parameters. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         Response object with JSON data containing calculated UL, CRUL, and FLUL values. | ||||||
|  |  | ||||||
|  |     Raises: | ||||||
|  |         ValueError if provided parameters are invalid. | ||||||
|  |     """ | ||||||
|  |     def get(self, request): | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             ra = float(request.query_params.get('ra')) | ||||||
|  |             dec = float(request.query_params.get('dec')) | ||||||
|  |             confidence_level = float(request.query_params.get('cl')) | ||||||
|  |         except (TypeError, ValueError): | ||||||
|  |             return Response( | ||||||
|  |                 {"error": "Invalud parameters, provide RA, DEC, and CL"}, | ||||||
|  |                 status = status.HTTP_400_BAD_REQUEST | ||||||
|  |             ) | ||||||
|  |          | ||||||
|  |         # hp = HEALPix( | ||||||
|  |         #     nside = 4096, | ||||||
|  |         #     order = 'ring', | ||||||
|  |         #     frame = 'icrs' | ||||||
|  |         # ) | ||||||
|  |          | ||||||
|  |         src_coord = SkyCoord( | ||||||
|  |             ra, dec, unit = 'deg', frame = 'icrs' | ||||||
|  |         ) | ||||||
|  |         gal = src_coord.galactic | ||||||
|  |         src_vec = hp.ang2vec(gal.l.deg, gal.b.deg, lonlat = True) | ||||||
|  |  | ||||||
|  |         aperture_radius = 50 # radius of the aperture in arc seconds | ||||||
|  |         # HPD ~48 arcseconds | ||||||
|  |         # 90% ~100 arcseconds | ||||||
|  |         annulus_inner = 100 # 2 * aperture_radius | ||||||
|  |         annulus_outer = 200 # 4 * aperture_radius | ||||||
|  |          | ||||||
|  |         source_pixel_list = hp.query_disc( | ||||||
|  |             nside = 4096, | ||||||
|  |             vec = src_vec, | ||||||
|  |             inclusive = False, | ||||||
|  |             nest = False, | ||||||
|  |             radius = (aperture_radius * u.arcsecond).to(u.radian).value | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |         inner_pixel_list = hp.query_disc( | ||||||
|  |             nside = 4096, | ||||||
|  |             vec = src_vec, | ||||||
|  |             inclusive = False, | ||||||
|  |             nest = False, | ||||||
|  |             radius = (annulus_inner * u.arcsecond).to(u.radian).value | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |         outer_pixel_list = hp.query_disc( | ||||||
|  |             nside = 4096, | ||||||
|  |             vec = src_vec, | ||||||
|  |             inclusive = False, | ||||||
|  |             nest = False, | ||||||
|  |             radius = (annulus_outer * u.arcsecond).to(u.radian).value | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |          | ||||||
|  |         annulus_pixel_list = [ | ||||||
|  |             item for item in outer_pixel_list if item not in inner_pixel_list | ||||||
|  |         ] | ||||||
|  |                  | ||||||
|  |                  | ||||||
|  |         source_pixels = Pixel.objects.filter(hpid__in = source_pixel_list)  | ||||||
|  |          | ||||||
|  |         annulus_pixels = Pixel.objects.filter(hpid__in = annulus_pixel_list) | ||||||
|  |          | ||||||
|  |          | ||||||
|  |         N = sum(obj.counts for obj in source_pixels) | ||||||
|  |          | ||||||
|  |         Nnpix = len(source_pixels) | ||||||
|  |          | ||||||
|  |         Bcounts = sum(obj.counts for obj in annulus_pixels) | ||||||
|  |          | ||||||
|  |         Bnpix = len(annulus_pixels) | ||||||
|  |          | ||||||
|  |         B = Bcounts / Bnpix * Nnpix | ||||||
|  |          | ||||||
|  |         tsum = sum(obj.exposure for obj in source_pixels) | ||||||
|  |          | ||||||
|  |         t = tsum / Nnpix | ||||||
|  |          | ||||||
|  |          | ||||||
|  |         EEF = .9      # eclosed energy fraction, .5 for hpd, .9 for w90 | ||||||
|  |         ECF = 4e-11   # energy conversion factor | ||||||
|  |          | ||||||
|  |         UL = ( | ||||||
|  |             sp.gammaincinv( | ||||||
|  |                 N + 1,  | ||||||
|  |                 confidence_level * sp.gammaincc(N + 1, B) + sp.gammainc(N + 1, B) | ||||||
|  |             ) - B | ||||||
|  |         )                   # count upper limit | ||||||
|  |                              | ||||||
|  |           | ||||||
|  |         CRUL = UL / t / EEF # count rate upper limit | ||||||
|  |          | ||||||
|  |         FLUL = CRUL * ECF   # flux upper limit | ||||||
|  |          | ||||||
|  |         return Response({ | ||||||
|  |             'UpperLimit' : UL, | ||||||
|  |             'CountRateUpperLimit' : CRUL, | ||||||
|  |             'FluxUpperLimit' : FLUL, | ||||||
|  |             'N' : N, | ||||||
|  |             'Nnpix' : Nnpix, | ||||||
|  |             'Bcounts' : Bcounts, | ||||||
|  |             'Bnpix' : Bnpix, | ||||||
|  |             'B' : B, | ||||||
|  |             'tsum' : tsum, | ||||||
|  |             't' : t | ||||||
|  |         }, status=status.HTTP_200_OK) | ||||||
		Reference in New Issue
	
	Block a user