mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-19 14:32:16 +00:00
Breakout (most) user stuff to separate module
The model remains in RIGS for now, as it's pretty painful to move...
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from RIGS import models, forms
|
||||
from users import forms as user_forms
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from reversion.admin import VersionAdmin
|
||||
@@ -21,7 +22,6 @@ admin.site.register(models.EventItem, VersionAdmin)
|
||||
admin.site.register(models.Invoice)
|
||||
admin.site.register(models.Payment)
|
||||
|
||||
|
||||
def approve_user(modeladmin, request, queryset):
|
||||
queryset.update(is_approved=True)
|
||||
|
||||
@@ -48,8 +48,8 @@ class ProfileAdmin(UserAdmin):
|
||||
'fields': ('username', 'password1', 'password2'),
|
||||
}),
|
||||
)
|
||||
form = forms.ProfileChangeForm
|
||||
add_form = forms.ProfileCreationForm
|
||||
form = user_forms.ProfileChangeForm
|
||||
add_form = user_forms.ProfileCreationForm
|
||||
actions = [approve_user]
|
||||
|
||||
|
||||
|
||||
@@ -16,54 +16,6 @@ forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||
forms.TimeField.widget = forms.TextInput(attrs={'type': 'time'})
|
||||
forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'})
|
||||
|
||||
# Registration
|
||||
|
||||
|
||||
class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
|
||||
captcha = ReCaptchaField()
|
||||
|
||||
class Meta:
|
||||
model = models.Profile
|
||||
fields = ('username', 'email', 'first_name', 'last_name', 'initials')
|
||||
|
||||
def clean_initials(self):
|
||||
"""
|
||||
Validate that the supplied initials are unique.
|
||||
"""
|
||||
if models.Profile.objects.filter(initials__iexact=self.cleaned_data['initials']):
|
||||
raise forms.ValidationError("These initials are already in use. Please supply different initials.")
|
||||
return self.cleaned_data['initials']
|
||||
|
||||
|
||||
class CheckApprovedForm(AuthenticationForm):
|
||||
def confirm_login_allowed(self, user):
|
||||
if user.is_approved or user.is_superuser:
|
||||
return AuthenticationForm.confirm_login_allowed(self, user)
|
||||
else:
|
||||
raise forms.ValidationError("Your account hasn't been approved by an administrator yet. Please check back in a few minutes!")
|
||||
|
||||
|
||||
# Embedded Login form - remove the autofocus
|
||||
class EmbeddedAuthenticationForm(CheckApprovedForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['username'].widget.attrs.pop('autofocus', None)
|
||||
|
||||
|
||||
class PasswordReset(PasswordResetForm):
|
||||
captcha = ReCaptchaField(label='Captcha')
|
||||
|
||||
|
||||
class ProfileCreationForm(UserCreationForm):
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = models.Profile
|
||||
|
||||
|
||||
class ProfileChangeForm(UserChangeForm):
|
||||
class Meta(UserChangeForm.Meta):
|
||||
model = models.Profile
|
||||
|
||||
|
||||
# Events Shit
|
||||
class EventForm(forms.ModelForm):
|
||||
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + list(settings.DATETIME_INPUT_FORMATS)
|
||||
|
||||
@@ -20,7 +20,6 @@ from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
from RIGS.models import Profile
|
||||
from RIGS.forms import ProfileRegistrationFormUniqueEmail
|
||||
from registration.signals import user_registered
|
||||
|
||||
|
||||
def user_created(sender, user, request, **kwargs):
|
||||
form = ProfileRegistrationFormUniqueEmail(request.POST)
|
||||
user.first_name = form.data['first_name']
|
||||
user.last_name = form.data['last_name']
|
||||
user.initials = form.data['initials']
|
||||
# user.phone = form.data['phone']
|
||||
user.save()
|
||||
|
||||
|
||||
user_registered.connect(user_created)
|
||||
@@ -1,12 +0,0 @@
|
||||
{# pass in variable "profile" to this template #}
|
||||
|
||||
<button title="{{profile.name}}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover focus' data-toggle="popover" data-content='
|
||||
<img src="{{profile.profile_picture}}" class="img-responsive img-rounded center-block" style="max-width:4em" />
|
||||
<dl class="dl-vertical">
|
||||
<dt>Email</dt>
|
||||
<dd>{{profile.email}}</dd>
|
||||
|
||||
<dt>Phone</dt>
|
||||
<dd>{{profile.phone}}</dd>
|
||||
</dl>
|
||||
'>{{profile.first_name}}</button>
|
||||
@@ -1,150 +0,0 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||
|
||||
{% block title %}RIGS Profile {{object.pk}}{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#urlParamForm').change(function(){
|
||||
url = "?";
|
||||
$('#urlParamForm *').filter(':input').each(function(index, value){
|
||||
defaultVal = $(value).data('default');
|
||||
param = $(value).val();
|
||||
val = $(value).prop('checked');
|
||||
|
||||
if(val != defaultVal){
|
||||
url = url+param+"="+val+"&";
|
||||
}
|
||||
});
|
||||
ics_url = $('#cal-url').data('url') + url.substring(0, url.length - 1);
|
||||
$('#cal-url').text(ics_url);
|
||||
|
||||
gcal_url = $('#gcal-link').data('url') + encodeURIComponent(url.substring(0, url.length - 1));
|
||||
$('#gcal-link').attr('href',gcal_url);
|
||||
});
|
||||
|
||||
$('#urlParamForm').change(); //Do the initial setting
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3>Profile: {{object.name}}</h3>
|
||||
{% if not request.is_ajax %}
|
||||
<div class="row py-3">
|
||||
{% if object.pk == user.pk %}
|
||||
<div>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'profile_update_self' %}" class="btn btn-light">
|
||||
Edit Profile <i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'password_change' %}" class="btn btn-light">
|
||||
Change Password <span class="fas fa-lock"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-3">
|
||||
<img src="{{object.profile_picture}}" class="card-img img-fluid" />
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-5">First Name</dt>
|
||||
<dd class="col-7">{{object.first_name}}</dd>
|
||||
|
||||
<dt class="col-5">Last Name</dt>
|
||||
<dd class="col-7">{{object.last_name}}</dd>
|
||||
|
||||
<dt class="col-5">Email</dt>
|
||||
<dd class="col-7">{{object.email}}</dd>
|
||||
|
||||
<dt class="col-5">Last Login</dt>
|
||||
<dd class="col-7">{{object.last_login|date:"d/m/Y H:i"}}</dd>
|
||||
|
||||
<dt class="col-5">Date Joined</dt>
|
||||
<dd class="col-7">{{object.date_joined|date:"d/m/Y H:i"}}</dd>
|
||||
|
||||
<dt class="col-5">Initials</dt>
|
||||
<dd class="col-7">{{object.initials}}</dd>
|
||||
|
||||
<dt class="col-5">Phone</dt>
|
||||
<dd class="col-7">{% if object.phone %}}<a href="tel:{{ object.phone }}">{% endif %}{{object.phone}}{% if object.phone %}}</a>{% endif %}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not request.is_ajax and object.pk == user.pk %}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4>Personal iCal Details</h4>
|
||||
<dl class="row">
|
||||
<dt class="col-4">API Key</dt>
|
||||
<dd class="col-5">
|
||||
{% if user.api_key %}
|
||||
{{user.api_key}}
|
||||
{% else %}
|
||||
No API Key Generated
|
||||
{% endif %}
|
||||
</dd>
|
||||
<a href="{% url 'reset_api_key' %}" class="btn btn-secondary col-3">
|
||||
{% if user.api_key %}Reset API Key{% else %}Generate API Key{% endif %}
|
||||
<span class="fas fa-redo"></span>
|
||||
</a>
|
||||
|
||||
<dt class="col-4">Calendar Options</dt>
|
||||
<dd class="col-8">
|
||||
<form class="form-inline" id="urlParamForm">
|
||||
<div class="form-group">
|
||||
<label class="checkbox-inline mr-3">
|
||||
<input type="checkbox" value="rig" data-default="true" checked> Rigs
|
||||
</label>
|
||||
<label class="checkbox-inline mx-3">
|
||||
<input type="checkbox" value="non-rig" data-default="true" checked> Non-Rigs
|
||||
</label>
|
||||
<label class="checkbox-inline mx-3">
|
||||
<input type="checkbox" value="dry-hire" data-default="true" checked> Dry-Hires
|
||||
</label>
|
||||
<label class="checkbox-inline mx-3">
|
||||
<input type="checkbox" value="cancelled" data-default="false" > Cancelled
|
||||
</label>
|
||||
<label class="checkbox-inline mx-3">
|
||||
<input type="checkbox" value="provisional" data-default="true" checked> Provisional
|
||||
</label>
|
||||
<label class="checkbox-inline mx-3">
|
||||
<input type="checkbox" value="confirmed" data-default="true" checked> Confirmed/Booked
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</dd>
|
||||
<dt class="col-4">Calendar URL</dt>
|
||||
<dd class="col-8">
|
||||
{% if user.api_key %}
|
||||
<pre id="cal-url" data-url="http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}"></pre>
|
||||
<small><a id="gcal-link" data-url="https://support.google.com/calendar/answer/37100" href="">Click here</a> for instructions on adding to google calendar.<br/>
|
||||
To sync from google calendar to mobile device, visit <a href="https://www.google.com/calendar/syncselect" target="_blank">this page</a> on your device and tick "RIGS Calendar".</small>
|
||||
{% else %}
|
||||
<pre>No API Key Generated</pre>
|
||||
{% endif %}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h4>Events</h4>
|
||||
{% with object.latest_events as events %}
|
||||
{% include 'event_table.html' %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
@@ -1,71 +0,0 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}Update Profile {{object.name}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
{% include 'form_errors.html' %}
|
||||
<h3>Update Profile</h3>
|
||||
<div class="col-sm-6">
|
||||
<form action="{{form.action|default:request.path}}" method="post" class="form-horizontal">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="{{form.first_name.id_for_label}}" class="col-sm-4 control-label">{{form.first_name.label}}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.first_name class+="form-control" placeholder=form.first_name.label %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.last_name.id_for_label}}" class="col-sm-4 control-label">{{form.last_name.label}}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.last_name class+="form-control" placeholder=form.last_name.label %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.email.id_for_label}}" class="col-sm-4 control-label">{{form.email.label}}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.email type="email" class+="form-control" placeholder=form.email.label %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.initials.id_for_label}}" class="col-sm-4 control-label">{{form.initials.label}}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.initials class+="form-control" placeholder=form.initials.label %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{form.phone.id_for_label}}" class="col-sm-4 control-label">{{form.phone.label}}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.phone type="tel" class+="form-control" placeholder=form.phone.label %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input class="btn btn-primary pull-right" type="submit"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-sm-offset-2">
|
||||
<div class="center-block">
|
||||
<a href="https://gravatar.com/">
|
||||
<img src="{{object.profile_picture}}" class="img-responsive img-rounded" />
|
||||
<div class="text-center">
|
||||
Images hosted by Gravatar
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -125,12 +125,6 @@ urlpatterns = [
|
||||
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
||||
name="ics_calendar"),
|
||||
|
||||
# API
|
||||
path('api/<str:model>/', login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
path('api/<str:model>/<int:pk>/', login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
|
||||
# Risk assessment API
|
||||
path('log_risk_assessment/', rigboard.LogRiskAssessment.as_view(), name='log_risk_assessment'),
|
||||
|
||||
|
||||
174
RIGS/views.py
174
RIGS/views.py
@@ -38,26 +38,6 @@ class Index(generic.TemplateView):
|
||||
class SearchHelp(generic.TemplateView):
|
||||
template_name = 'search_help.html'
|
||||
|
||||
|
||||
# This view should be exempt from requiring CSRF token.
|
||||
# Then we can check for it and show a nice error
|
||||
# Don't worry, django.contrib.auth.views.login will
|
||||
# check for it before logging the user in
|
||||
class LoginEmbed(LoginView):
|
||||
template_name = 'registration/login_embed.html'
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.method == "POST":
|
||||
csrf_cookie = request.COOKIES.get('csrftoken', None)
|
||||
|
||||
if csrf_cookie is None:
|
||||
messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.')
|
||||
request.method = 'GET' # Render the page without trying to login
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
"""
|
||||
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
||||
May optionally also include some javascript in a success message to cause a load of
|
||||
@@ -270,157 +250,3 @@ class VenueUpdate(generic.UpdateView):
|
||||
'pk': self.object.pk,
|
||||
})
|
||||
return url
|
||||
|
||||
|
||||
class SecureAPIRequest(generic.View):
|
||||
models = {
|
||||
'venue': models.Venue,
|
||||
'person': models.Person,
|
||||
'organisation': models.Organisation,
|
||||
'profile': models.Profile,
|
||||
'event': models.Event,
|
||||
'supplier': asset_models.Supplier
|
||||
}
|
||||
|
||||
perms = {
|
||||
'venue': 'RIGS.view_venue',
|
||||
'person': 'RIGS.view_person',
|
||||
'organisation': 'RIGS.view_organisation',
|
||||
'profile': 'RIGS.view_profile',
|
||||
'event': None,
|
||||
'supplier': None
|
||||
}
|
||||
|
||||
'''
|
||||
Validate the request is allowed based on user permissions.
|
||||
Raises 403 if denied.
|
||||
Potential to add API key validation at a later date.
|
||||
'''
|
||||
|
||||
def __validate__(self, request, key, perm):
|
||||
if request.user.is_active:
|
||||
if request.user.is_superuser or perm is None:
|
||||
return True
|
||||
elif request.user.has_perm(perm):
|
||||
return True
|
||||
raise PermissionDenied()
|
||||
|
||||
def get(self, request, model, pk=None, param=None):
|
||||
# Request permission validation things
|
||||
key = request.GET.get('apikey', None)
|
||||
perm = self.perms[model]
|
||||
self.__validate__(request, key, perm)
|
||||
|
||||
# Response format where applicable
|
||||
format = request.GET.get('format', 'json')
|
||||
fields = request.GET.get('fields', None)
|
||||
if fields:
|
||||
fields = fields.split(",")
|
||||
|
||||
# Supply data for one record
|
||||
if pk:
|
||||
object = get_object_or_404(self.models[model], pk=pk)
|
||||
data = serializers.serialize(format, [object], fields=fields)
|
||||
return HttpResponse(data, content_type="application/" + format)
|
||||
|
||||
# Supply data for autocomplete ajax request in json form
|
||||
term = request.GET.get('q', None)
|
||||
if term:
|
||||
if fields is None: # Default to just name
|
||||
fields = ['name']
|
||||
|
||||
# Build a list of Q objects for use later
|
||||
queries = []
|
||||
for part in term.split(" "):
|
||||
qs = []
|
||||
for field in fields:
|
||||
q = Q(**{field + "__icontains": part})
|
||||
qs.append(q)
|
||||
queries.append(reduce(operator.or_, qs))
|
||||
|
||||
# Build the data response list
|
||||
results = []
|
||||
query = reduce(operator.and_, queries)
|
||||
objects = self.models[model].objects.filter(query)
|
||||
for o in objects:
|
||||
data = {
|
||||
'pk': o.pk,
|
||||
'value': o.pk,
|
||||
'text': o.name,
|
||||
}
|
||||
try: # See if there is a valid update URL
|
||||
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
results.append(data)
|
||||
|
||||
# return a data response
|
||||
json = simplejson.dumps(results)
|
||||
return HttpResponse(json, content_type="application/json") # Always json
|
||||
|
||||
start = request.GET.get('start', None)
|
||||
end = request.GET.get('end', None)
|
||||
|
||||
if model == "event" and start and end:
|
||||
# Probably a calendar request
|
||||
start_datetime = datetime.datetime.strptime(start, "%Y-%m-%dT%H:%M:%S")
|
||||
end_datetime = datetime.datetime.strptime(end, "%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
objects = self.models[model].objects.events_in_bounds(start_datetime, end_datetime)
|
||||
|
||||
results = []
|
||||
for item in objects:
|
||||
data = {
|
||||
'pk': item.pk,
|
||||
'title': item.name,
|
||||
'is_rig': item.is_rig,
|
||||
'status': str(item.get_status_display()),
|
||||
'earliest': item.earliest_time.isoformat(),
|
||||
'latest': item.latest_time.isoformat(),
|
||||
'url': str(item.get_absolute_url())
|
||||
}
|
||||
|
||||
results.append(data)
|
||||
json = simplejson.dumps(results)
|
||||
return HttpResponse(json, content_type="application/json") # Always json
|
||||
|
||||
return HttpResponse(model)
|
||||
|
||||
|
||||
class ProfileDetail(generic.DetailView):
|
||||
template_name = "profile_detail.html"
|
||||
model = models.Profile
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
pk = self.kwargs['pk']
|
||||
except KeyError:
|
||||
pk = self.request.user.id
|
||||
self.kwargs['pk'] = pk
|
||||
|
||||
return self.model.objects.filter(pk=pk)
|
||||
|
||||
|
||||
class ProfileUpdateSelf(generic.UpdateView):
|
||||
template_name = "profile_form.html"
|
||||
model = models.Profile
|
||||
fields = ['first_name', 'last_name', 'email', 'initials', 'phone']
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.request.user.id
|
||||
self.kwargs['pk'] = pk
|
||||
|
||||
return self.model.objects.filter(pk=pk)
|
||||
|
||||
def get_success_url(self):
|
||||
url = reverse_lazy('profile_detail')
|
||||
return url
|
||||
|
||||
|
||||
class ResetApiKey(generic.RedirectView):
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
self.request.user.api_key = self.request.user.make_api_key()
|
||||
|
||||
self.request.user.save()
|
||||
|
||||
return reverse_lazy('profile_detail')
|
||||
|
||||
Reference in New Issue
Block a user