Merge pull request #104 from satnogs/test-job
[Fixes #102] Test client connectivitymerge-requests/118/head
commit
4143a73c71
|
@ -12,4 +12,4 @@ See the [documentation](http://docs.satnogs.org/network/).
|
|||
|
||||
© 2014-2015 [Libre Space Foundation](http://librespacefoundation.org).
|
||||
|
||||
Licensed under the [MPL-2.0](LICENSE).
|
||||
Licensed under the [AGPLv3](LICENSE).
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import django_filters
|
||||
|
||||
from network.base.models import Data
|
||||
|
||||
|
||||
class DataViewFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Data
|
||||
fields = ['start', 'end', 'ground_station']
|
|
@ -10,13 +10,23 @@ class SafeMethodsOnlyPermission(permissions.BasePermission):
|
|||
return request.method in permissions.SAFE_METHODS
|
||||
|
||||
|
||||
class StationOwnerCanEditPermission(SafeMethodsOnlyPermission):
|
||||
"""Only the owner can push new data"""
|
||||
class StationOwnerCanViewPermission(permissions.BasePermission):
|
||||
"""Only the owner can view station jobs"""
|
||||
def has_object_permission(self, request, view, obj=None):
|
||||
if obj is None:
|
||||
can_edit = True
|
||||
else:
|
||||
can_edit = request.user == obj.observation.author
|
||||
return (can_edit or
|
||||
super(StationOwnerCanEditPermission,
|
||||
self).has_object_permission(request, view, obj))
|
||||
can_edit = request.user == obj.ground_station.owner
|
||||
return can_edit
|
||||
|
||||
|
||||
class StationOwnerCanEditPermission(permissions.BasePermission):
|
||||
"""Only the owner can edit station jobs"""
|
||||
def has_object_permission(self, request, view, obj=None):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
if obj is None:
|
||||
can_edit = True
|
||||
else:
|
||||
can_edit = request.user == obj.ground_station.owner
|
||||
return can_edit
|
||||
|
|
|
@ -49,6 +49,7 @@ class DataSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Data
|
||||
fields = ('id', 'start', 'end', 'observation', 'ground_station', 'payload')
|
||||
read_only_fields = ['id', 'start', 'end', 'observation', 'ground_station']
|
||||
|
||||
|
||||
class JobSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -7,13 +7,14 @@ from network.api import views
|
|||
router = routers.DefaultRouter()
|
||||
|
||||
router.register(r'antennas', views.AntennaView)
|
||||
router.register(r'data', views.DataView)
|
||||
router.register(r'observations', views.ObservationView)
|
||||
router.register(r'satellites', views.SatelliteView)
|
||||
router.register(r'stations', views.StationView)
|
||||
router.register(r'transponders', views.TransponderView)
|
||||
router.register(r'data', views.DataView)
|
||||
router.register(r'jobs', views.JobView)
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^', include(router.urls))
|
||||
|
|
|
@ -1,65 +1,55 @@
|
|||
from django.utils.timezone import now
|
||||
|
||||
import django_filters
|
||||
from rest_framework import viewsets, mixins
|
||||
|
||||
from network.api.perms import StationOwnerCanEditPermission
|
||||
from network.api import serializers
|
||||
from network.api.perms import StationOwnerCanViewPermission, StationOwnerCanEditPermission
|
||||
from network.api import serializers, filters
|
||||
from network.base.models import (Antenna, Data, Observation, Satellite,
|
||||
Station, Transponder)
|
||||
|
||||
|
||||
class AntennaView(viewsets.ModelViewSet):
|
||||
class AntennaView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Antenna.objects.all()
|
||||
serializer_class = serializers.AntennaSerializer
|
||||
|
||||
|
||||
class StationView(viewsets.ModelViewSet):
|
||||
class StationView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Station.objects.all()
|
||||
serializer_class = serializers.StationSerializer
|
||||
|
||||
|
||||
class SatelliteView(viewsets.ModelViewSet):
|
||||
class SatelliteView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Satellite.objects.all()
|
||||
serializer_class = serializers.SatelliteSerializer
|
||||
#permission_classes = [SafeMethodsOnlyPermission]
|
||||
|
||||
|
||||
class TransponderView(viewsets.ModelViewSet):
|
||||
class TransponderView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Transponder.objects.all()
|
||||
serializer_class = serializers.TransponderSerializer
|
||||
|
||||
|
||||
class ObservationView(viewsets.ModelViewSet):
|
||||
class ObservationView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Observation.objects.all()
|
||||
serializer_class = serializers.ObservationSerializer
|
||||
|
||||
|
||||
class DataFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Data
|
||||
fields = ['start', 'end', 'ground_station']
|
||||
|
||||
|
||||
class DataView(viewsets.ReadOnlyModelViewSet, mixins.UpdateModelMixin):
|
||||
class DataView(viewsets.ModelViewSet, mixins.UpdateModelMixin):
|
||||
queryset = Data.objects.all()
|
||||
serializer_class = serializers.DataSerializer
|
||||
filter_class = DataFilter
|
||||
permission_classes = [
|
||||
StationOwnerCanEditPermission
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
payload = self.request.query_params.get('payload', None)
|
||||
if payload == '':
|
||||
return self.queryset.filter(payload='')
|
||||
return super(DataView, self).get_queryset()
|
||||
|
||||
|
||||
class JobView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Data.objects.filter(payload='')
|
||||
serializer_class = serializers.JobSerializer
|
||||
filter_class = DataFilter
|
||||
filter_class = filters.DataViewFilter
|
||||
filter_fields = ('ground_station')
|
||||
permission_classes = [
|
||||
StationOwnerCanViewPermission
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(start__gte=now())
|
||||
|
|
|
@ -7,5 +7,5 @@ class StationForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Station
|
||||
fields = ['name', 'image', 'alt',
|
||||
'lat', 'lng', 'qthlocator', 'antenna', 'online']
|
||||
'lat', 'lng', 'qthlocator', 'antenna', 'active']
|
||||
image = forms.ImageField(required=False)
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import signal
|
||||
import sys
|
||||
from optparse import make_option
|
||||
from orbit import satellite
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from network.base.models import Satellite
|
||||
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
print('You pressed Ctrl+C!')
|
||||
sys.exit(0)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--delete',
|
||||
action='store_true',
|
||||
dest='delete',
|
||||
default=False,
|
||||
help='Delete Satellites'),
|
||||
)
|
||||
args = '<Satellite Range>'
|
||||
help = 'Updates/Inserts TLEs for a range of Satellites (eg. xxxx:xxxx)'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for arg in args:
|
||||
try:
|
||||
start, end = arg.split(':')
|
||||
except ValueError:
|
||||
raise CommandError('You need to spacify the range in the form: xxxx:xxxx')
|
||||
|
||||
r = range(int(start), int(end))
|
||||
for item in r:
|
||||
if options['delete']:
|
||||
try:
|
||||
Satellite.objects.get(norad_cat_id=item).delete()
|
||||
self.stdout.write('Satellite {}: deleted'.format(item))
|
||||
continue
|
||||
except:
|
||||
self.stdout.write('Satellite with Identifier {} does not exist'.format(item))
|
||||
continue
|
||||
|
||||
try:
|
||||
sat = satellite(item)
|
||||
except:
|
||||
self.stdout.write('Satellite with Identifier {} does not exist'.format(item))
|
||||
continue
|
||||
|
||||
try:
|
||||
obj = Satellite.objects.get(norad_cat_id=item)
|
||||
except:
|
||||
obj = Satellite(norad_cat_id=item)
|
||||
|
||||
obj.name = sat.name()
|
||||
tle = sat.tle()
|
||||
obj.tle0 = tle[0]
|
||||
obj.tle1 = tle[1]
|
||||
obj.tle2 = tle[2]
|
||||
obj.save()
|
||||
|
||||
self.stdout.write('fetched data for {}: {}'.format(obj.norad_cat_id, obj.name))
|
|
@ -10,7 +10,7 @@ class Command(BaseCommand):
|
|||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
satellites = Satellite.objets.all()
|
||||
satellites = Satellite.objetcs.all()
|
||||
|
||||
for obj in satellites:
|
||||
try:
|
||||
|
@ -27,4 +27,4 @@ class Command(BaseCommand):
|
|||
obj.tle2 = tle[2]
|
||||
obj.save()
|
||||
self.stdout.write(('Satellite {} with Identifier {} '
|
||||
'found [updated]').format(obj.norad_cat_id, obj.name))
|
||||
'found [updated]').format(obj.norad_cat_id, obj.name))
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0015_station_qthlocator'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='station',
|
||||
name='online',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='station',
|
||||
name='active',
|
||||
field=models.BooleanField(default=False),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='station',
|
||||
name='last_seen',
|
||||
field=models.DateTimeField(null=True, blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='updated',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -2,12 +2,13 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.core.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0015_station_qthlocator'),
|
||||
('base', '0016_auto_20150416_0758'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -46,4 +47,14 @@ class Migration(migrations.Migration):
|
|||
field=models.CharField(default='', max_length=255, blank=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transponder',
|
||||
name='baud',
|
||||
field=models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transponder',
|
||||
name='mode',
|
||||
field=models.CharField(blank=True, max_length=10, choices=[(b'FM', b'FM'), (b'AFSK', b'AFSK'), (b'BFSK', b'BFSK'), (b'APRS', b'APRS'), (b'SSTV', b'SSTV'), (b'CW', b'CW'), (b'FMN', b'FMN')]),
|
||||
),
|
||||
]
|
|
@ -1,3 +1,5 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
|
@ -44,8 +46,8 @@ class Station(models.Model):
|
|||
'contact SatNOGS Team'))
|
||||
featured_date = models.DateField(null=True, blank=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
online = models.BooleanField(default=False,
|
||||
help_text='Is your Ground Station functional?')
|
||||
active = models.BooleanField(default=False)
|
||||
last_seen = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def get_image(self):
|
||||
if self.image and hasattr(self.image, 'url'):
|
||||
|
@ -53,6 +55,14 @@ class Station(models.Model):
|
|||
else:
|
||||
return settings.STATION_DEFAULT_IMAGE
|
||||
|
||||
@property
|
||||
def online(self):
|
||||
try:
|
||||
heartbeat = self.last_seen + timedelta(minutes=settings.STATION_HEARTBEAT_TIME)
|
||||
return self.active and heartbeat > now()
|
||||
except:
|
||||
return False
|
||||
|
||||
def __unicode__(self):
|
||||
return "%d - %s" % (self.pk, self.name)
|
||||
|
||||
|
@ -64,7 +74,7 @@ class Satellite(models.Model):
|
|||
tle0 = models.CharField(max_length=100, blank=True)
|
||||
tle1 = models.CharField(max_length=200, blank=True)
|
||||
tle2 = models.CharField(max_length=200, blank=True)
|
||||
updated = models.DateTimeField(auto_now_add=True, blank=True)
|
||||
updated = models.DateTimeField(auto_now=True, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
@ -79,9 +89,9 @@ class Transponder(models.Model):
|
|||
downlink_low = models.PositiveIntegerField(blank=True, null=True)
|
||||
downlink_high = models.PositiveIntegerField(blank=True, null=True)
|
||||
mode = models.CharField(choices=zip(MODE_CHOICES, MODE_CHOICES),
|
||||
max_length=10)
|
||||
max_length=10, blank=True)
|
||||
invert = models.BooleanField(default=False)
|
||||
baud = models.FloatField(validators=[MinValueValidator(0)])
|
||||
baud = models.FloatField(validators=[MinValueValidator(0)], null=True, blank=True)
|
||||
satellite = models.ForeignKey(Satellite, related_name='transponder',
|
||||
null=True)
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ class StationFactory(factory.django.DjangoModelFactory):
|
|||
lat = fuzzy.FuzzyFloat(-20, 70)
|
||||
lng = fuzzy.FuzzyFloat(-180, 180)
|
||||
featured_date = fuzzy.FuzzyDateTime(now() - timedelta(days=10), now())
|
||||
online = fuzzy.FuzzyChoice(choices=[True, False])
|
||||
active = fuzzy.FuzzyChoice(choices=[True, False])
|
||||
last_seen = fuzzy.FuzzyDateTime(now() - timedelta(days=10), now())
|
||||
|
||||
@factory.post_generation
|
||||
def antennas(self, create, extracted, **kwargs):
|
||||
|
|
|
@ -18,7 +18,7 @@ from network.base.forms import StationForm
|
|||
def index(request):
|
||||
"""View to render index page."""
|
||||
observations = Observation.objects.all()
|
||||
featured_station = Station.objects.filter(online=True).latest('featured_date')
|
||||
featured_station = Station.objects.filter(active=True).latest('featured_date')
|
||||
|
||||
ctx = {
|
||||
'latest_observations': observations.filter(end__lt=now()),
|
||||
|
@ -74,7 +74,6 @@ def observation_new(request):
|
|||
satellites = Satellite.objects.filter(transponder__alive=True)
|
||||
transponders = Transponder.objects.filter(alive=True)
|
||||
|
||||
|
||||
return render(request, 'base/observation_new.html',
|
||||
{'satellites': satellites,
|
||||
'transponders': transponders,
|
||||
|
@ -98,6 +97,8 @@ def prediction_windows(request, sat_id, start_date, end_date):
|
|||
|
||||
stations = Station.objects.all()
|
||||
for station in stations:
|
||||
if not station.online:
|
||||
continue
|
||||
observer = ephem.Observer()
|
||||
observer.lon = str(station.lng)
|
||||
observer.lat = str(station.lat)
|
||||
|
@ -192,7 +193,12 @@ def station_edit(request):
|
|||
f.owner = request.user
|
||||
f.save()
|
||||
form.save_m2m()
|
||||
messages.success(request, 'Successfully saved Ground Station')
|
||||
if f.online:
|
||||
messages.success(request, 'Successfully saved Ground Station.')
|
||||
else:
|
||||
messages.success(request, ('Successfully saved Ground Station. It will appear online '
|
||||
'as soon as it connects with our API.'))
|
||||
|
||||
return redirect(reverse('base:station_view', kwargs={'id': f.id}))
|
||||
else:
|
||||
messages.error(request, 'Some fields missing on the form')
|
||||
|
|
|
@ -151,6 +151,9 @@ REST_FRAMEWORK = {
|
|||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
),
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
'rest_framework.filters.DjangoFilterBackend',
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -162,10 +165,6 @@ import dj_database_url
|
|||
DATABASE_URL = getenv('DATABASE_URL', 'sqlite:///db.sqlite3')
|
||||
DATABASES = {'default': dj_database_url.parse(DATABASE_URL)}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
|
||||
}
|
||||
|
||||
# Mapbox API
|
||||
MAPBOX_GEOCODE_URL = 'https://api.tiles.mapbox.com/v4/geocode/mapbox.places/'
|
||||
MAPBOX_MAP_ID = getenv('MAPBOX_MAP_ID', '')
|
||||
|
@ -174,3 +173,6 @@ MAPBOX_TOKEN = getenv('MAPBOX_TOKEN', '')
|
|||
# Observations datetimes in minutes
|
||||
DATE_MIN_START = '60'
|
||||
DATE_MAX_RANGE = '480'
|
||||
|
||||
# Station heartbeat in minutes
|
||||
STATION_HEARTBEAT_TIME = 60
|
||||
|
|
|
@ -94,18 +94,18 @@
|
|||
<div class="container">
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<span class="glyphicon glyphicon-copyright-mark" aria-hidden="true"></span> 2014<script>document.write("-"+new Date().getFullYear());</script>
|
||||
<a href="http://librespacefoundation.org/" target="_blank">Libre Space Foundation</a>.<br>
|
||||
<span class="glyphicon glyphicon-cloud" aria-hidden="true"></span>
|
||||
Observation data are freely distributed under the
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA</a> license.
|
||||
<div class="col-md-6">
|
||||
<span class="glyphicon glyphicon-copyright-mark" aria-hidden="true"></span> 2014<script>document.write("-"+new Date().getFullYear());</script>
|
||||
<a href="http://librespacefoundation.org/" target="_blank">Libre Space Foundation</a>.<br>
|
||||
<span class="glyphicon glyphicon-cloud" aria-hidden="true"></span>
|
||||
Observation data are freely distributed under the
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA</a> license.
|
||||
</div>
|
||||
<div class="col-md-6 text-right footer-options">
|
||||
<a href="https://github.com/satnogs/satnogs-network">Contribute</a> |
|
||||
<a href="#top">Back to top</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 text-right footer-options">
|
||||
<a href="https://github.com/satnogs/satnogs-network">Contribute</a> |
|
||||
<a href="#top">Back to top</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
@ -10,16 +10,29 @@
|
|||
{% endblock css %}
|
||||
|
||||
{% block content %}
|
||||
<h2 id="station-info"
|
||||
data-name="{{ station.name }}"
|
||||
data-id="{{ station.id }}"
|
||||
data-lng="{{ station.lng }}"
|
||||
data-lat="{{ station.lat }}">
|
||||
{{ station.id }} - {{ station.name }}
|
||||
{% if request.user == station.owner %}
|
||||
<button class="btn btn-primary pull-right" data-toggle="modal" data-target="#StationModal">Edit Ground Station</button>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 id="station-info"
|
||||
data-name="{{ station.name }}"
|
||||
data-id="{{ station.id }}"
|
||||
data-lng="{{ station.lng }}"
|
||||
data-lat="{{ station.lat }}">
|
||||
{{ station.id }} - {{ station.name }}
|
||||
{% if station.online %}
|
||||
<span class="label label-success">Online</span>
|
||||
{% else %}
|
||||
<span class="label label-danger">Offline</button>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<h2>
|
||||
{% if request.user == station.owner %}
|
||||
<button class="btn btn-primary" data-toggle="modal" data-target="#StationModal">Edit Ground Station</button>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
|
@ -66,6 +79,13 @@
|
|||
{{ station.created|timesince }} ago
|
||||
</span>
|
||||
</div>
|
||||
<div class="gs-front-line">
|
||||
<span class="label label-default">Last Seen</span>
|
||||
<span class="gs-front-data"
|
||||
title="{{ station.last_seen|date:"c" }}">
|
||||
{{ station.last_seen|timesince }} ago
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% for antenna in station.antenna.all %}
|
||||
|
|
|
@ -73,8 +73,8 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="online" {% if form.online.value %}checked="True"{% endif %}> Online Ground Station
|
||||
<input type="checkbox" name="active" {% if form.active.value %}checked="True"{% endif %}">
|
||||
Is it operational?
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
{% block branding %}
|
||||
<a class="navbar-brand" rel="nofollow" href="#">
|
||||
SatNOGS Network API <span class="version"></span>
|
||||
SatNOGS Network API <span class="version">1</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -169,4 +169,4 @@
|
|||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/gridsquare.js' %}"></script>
|
||||
{% endblock javascript %}
|
||||
{% endblock javascript %}
|
||||
|
|
|
@ -2,5 +2,5 @@ from allauth.account.adapter import DefaultAccountAdapter
|
|||
|
||||
|
||||
class NoSignupsAdapter(DefaultAccountAdapter):
|
||||
def is_open_for_signup(self, request):
|
||||
def is_open_for_signup(self, request):
|
||||
return False
|
||||
|
|
Loading…
Reference in New Issue