class Meta: model = GeographicRegion fields = tuple([ 'id', 'name', 'slug', 'code', 'hidden', 'level', 'geom', 'site', 'centroid', 'envelope', 'parent', 'parent__name', 'languages_available' ] + generate_translated_fields('title'))
class Meta: model = Provider fields = tuple([ 'url', 'id', ] + generate_translated_fields('name') + generate_translated_fields('description') + generate_translated_fields('focal_point_name') + generate_translated_fields('address') + [ 'type', 'phone_number', 'website', 'facebook', 'twitter', 'focal_point_phone_number', 'user', 'number_of_monthly_beneficiaries', 'is_frozen', 'service_types', 'meta_population', 'record', 'requirement', 'vacancy', 'additional_info' ]) required_translated_fields = [ 'name', 'description', 'focal_point_name', 'address' ]
def test_translated_fields(self): # Test getters and setters for each language in the settings nationality, _ = Nationality.objects.get_or_create(number=1) name_fields = generate_translated_fields('name', False) for field in name_fields: setattr(nationality, field, field) nationality.save() for field in name_fields: self.assertEqual(getattr(nationality, field), field)
def test_provider_change_own_data(self): # Non-staff can change their own provider provider = ProviderFactory(user=self.user) name_fields = generate_translated_fields('name', False) # Tweak some data for field in name_fields: setattr(provider, field, random_string(10)) book = get_export_workbook([provider]) rsp = self.import_book(book) self.assertEqual(OK, rsp.status_code, msg=rsp.content.decode('utf-8')) new_provider = Provider.objects.get(id=provider.id) for field in name_fields: self.assertEqual(getattr(provider, field), getattr(new_provider, field))
def run_validation(self, data=serializers.empty): # data is a dictionary errs = defaultdict(list) for field in self.Meta.required_translated_fields: if not any( data.get(key, False) for key in generate_translated_fields(field, False)): errs[field].append(_('This field is required.')) try: validated_data = super().run_validation(data) except (exceptions.ValidationError, DjangoValidationError) as exc: errs.update(serializers.get_validation_error_detail(exc)) if errs: raise exceptions.ValidationError(errs) return validated_data
def test_provider_bad_criteria(self): provider = ProviderFactory(user=self.user) service = ServiceFactory(provider=provider, status=Service.STATUS_CURRENT) criterion1 = SelectionCriterionFactory(service=service) criterion2 = SelectionCriterionFactory(service=service) # Change the 2nd one's text before exporting for field in generate_translated_fields('text', False): setattr(criterion2, field, '') book = get_export_workbook([provider], None, [criterion1, criterion2]) rsp = self.import_book(book) self.assertContains( rsp, "Selection criterion must have text in at least one language", status_code=BAD_REQUEST, msg_prefix=rsp.content.decode('utf-8'))
def test_staff_change_provider_invalid_id(self): self.user.is_staff = True self.user.save() provider = ProviderFactory() name_fields = generate_translated_fields('name', False) # Tweak some data for field in name_fields: setattr(provider, field, random_string(10)) book = get_export_workbook([provider], cell_overwrite_ok=True) sheet = book.get_sheet(0) sheet.write(r=1, c=0, label='xyz') rsp = self.import_book(book) self.assertContains(rsp, "id: xyz is not a valid ID", status_code=BAD_REQUEST, msg_prefix=rsp.content.decode('utf-8'))
def test_staff_change_provider(self): # Staff can change another user's provider self.user.is_staff = True self.user.save() provider = ProviderFactory() name_fields = generate_translated_fields('name', False) # Tweak some data for field in name_fields: setattr(provider, field, random_string(10)) book = get_export_workbook([provider]) rsp = self.import_book(book) self.assertEqual(OK, rsp.status_code, msg=rsp.content.decode('utf-8')) new_provider = Provider.objects.get(id=provider.id) for field in name_fields: self.assertEqual(getattr(provider, field), getattr(new_provider, field))
def test_staff_change_nonexistent_provider(self): # Staff can change another user's provider self.user.is_staff = True self.user.save() provider = ProviderFactory() name_fields = generate_translated_fields('name', False) # Tweak some data for field in name_fields: setattr(provider, field, random_string(10)) book = get_export_workbook([provider]) provider_id = provider.id provider.delete() rsp = self.import_book(book) self.assertContains(rsp, "There is no provider with id=%d" % provider_id, status_code=BAD_REQUEST, msg_prefix=rsp.content.decode('utf-8'))
def test_staff_change_providers(self): # Staff can change multiple providers self.user.is_staff = True self.user.save() provider1 = ProviderFactory() provider2 = ProviderFactory() name_fields = generate_translated_fields('name', False) # Tweak some data for field in name_fields: setattr(provider1, field, random_string(10)) provider2.number_of_monthly_beneficiaries = 1024 provider2.type = ProviderTypeFactory() book = get_export_workbook([provider1, provider2]) rsp = self.import_book(book) self.assertEqual(OK, rsp.status_code, msg=rsp.content.decode('utf-8')) new_provider1 = Provider.objects.get(id=provider1.id) for field in name_fields: self.assertEqual(getattr(provider1, field), getattr(new_provider1, field)) new_provider2 = Provider.objects.get(id=provider2.id) self.assertEqual(provider2.number_of_monthly_beneficiaries, new_provider2.number_of_monthly_beneficiaries)
def test_approval_validation(self): service = ServiceFactory(location=None) # No location - should not allow approval try: service.validate_for_approval() except ValidationError as e: self.assertIn('location', e.error_dict) else: self.fail("Should have gotten ValidationError") # Add location, should be okay service.location = 'POINT(5 23)' service.validate_for_approval() # No name, should fail name_fields = generate_translated_fields('name', False) for field in name_fields: setattr(service, field, '') try: service.validate_for_approval() except ValidationError as e: self.assertIn('name', e.error_dict) else: self.fail("Should have gotten ValidationError")
class PrivateServiceViewSet(FilterByRegionMixin, viewsets.ModelViewSet): filter_class = PrivateServiceFilter queryset = Service.objects.select_related( 'provider', 'type', 'region', ).prefetch_related('selection_criteria', 'tags', 'types', 'contact_information').all() serializer_class = serializers_v2.ServiceSerializer pagination_class = StandardResultsSetPagination filter_backends = (django_filters.DjangoFilterBackend, filters.OrderingFilter, SearchFilter) search_fields = ['name'] + generate_translated_fields('title', False) def get_queryset(self): qs = super(PrivateServiceViewSet, self).get_queryset() if not (hasattr(self.request, 'user') and self.request.user.is_superuser): providers = self.request.user.all_providers qs = qs.filter( Q(status__in=[Service.STATUS_CURRENT]) | Q(status__in=[Service.STATUS_PRIVATE], provider__in=providers)) return qs
class SelectionCriterionInlineAdmin(admin.TabularInline): model = SelectionCriterion fields = generate_translated_fields('text', False)
class GeographicRegionViewSet(viewsets.ModelViewSet): permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly] queryset = GeographicRegion.objects.select_related('parent').all() serializer_class = serializers_v2.GeographicRegionSerializer pagination_class = StandardResultsSetPagination filter_class = GeographicRegionFilter search_fields = ['name'] + generate_translated_fields('title', False) def get_serializer_class(self): if getattr(self, 'action') == 'create': return serializers_v2.GeographicRegionCreateSerializer if ('exclude_geometry' in self.request.GET or 'countries' in self.request.GET): return serializers_v2.GeographicRegionSerializerNoGeometry else: return serializers_v2.GeographicRegionSerializer def get_queryset(self): qs = super(GeographicRegionViewSet, self).get_queryset() if 'countries' in self.request.GET: qs = qs.filter(Q(level=1) & Q(hidden=False)) if (hasattr(self.request, 'parent')): qs = qs.filter( Q(parent=self.request.parent) | Q(parent__parent=self.request.parent)) return qs def update(self, request, *args, **kwargs): instance = self.get_object() # serializer with geom for return only serializerReturn = self.get_serializer(instance, data=request.data, partial=True) serializerReturn.is_valid(raise_exception=True) data = serializerReturn.data geom = instance.geom.ewkt.split(";")[1] geomobj = instance.geom instance.geom = None request.data.pop('geom') region = kwargs.pop('pk') # serializer without geom objet to save serializer = self.get_serializer(instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) TypesOrdering.objects.filter(region=region).delete() types_ordering = request.data['types_ordering'] for i, obj in enumerate(types_ordering): t = TypesOrdering(ordering=i, region_id=region, service_type_id=obj['id']) t.save() self.perform_update(serializer) cursor = connections['default'].cursor() cursor.execute( "update regions_geographicregion set geom = ST_GEOMFROMTEXT(%s, 4326) where id = %s ;", [geom, region]) self.geom = geomobj return Response(data)
from django.db import transaction from django.utils.translation import ugettext as _ from xlrd import open_workbook, XLRDError import xlwt from api.utils import generate_translated_fields from services.forms import ProviderForm, ServiceForm, SelectionCriterionForm from services.models import Service, SelectionCriterion, Provider logger = logging.getLogger(__name__) PROVIDER_SHEET_NAME = "Providers" PROVIDER_HEADINGS = [ 'id', ] + generate_translated_fields('name', False) + [ 'type__number', ] + generate_translated_fields('type__name', False) + [ 'phone_number', 'website', ] + generate_translated_fields('description', False) \ + generate_translated_fields('focal_point_name', False) + [ 'focal_point_phone_number' ] + generate_translated_fields('address', False) + [ 'email', 'number_of_monthly_beneficiaries', 'password' ] SERVICES_SHEET_NAME = 'Services' SERVICE_HEADINGS = [
class Meta: model = ServiceArea fields = tuple(['id', 'parent', 'url'] + generate_translated_fields('name')) required_translated_fields = ['name']
class ProviderViewSet(FilterByRegionMixin, viewsets.ModelViewSet): # queryset = Provider.objects.prefetch_related('services').all() queryset = Provider.objects.all() serializer_class = serializers_v2.ProviderSerializer pagination_class = StandardResultsSetPagination # All the text fields that are used for full-text searches (?search=XXXXX) search_fields = generate_translated_fields('name', False) \ + generate_translated_fields('description', False) \ + generate_translated_fields('focal_point_name', False) \ + generate_translated_fields('address', False) \ + generate_translated_fields('type__name', False) \ + ['phone_number'] \ + ['website'] \ + ['number_of_monthly_beneficiaries'] \ + ['focal_point_phone_number'] def update(self, request, *args, **kwargs): """On change to provider via the API, notify via JIRA""" response = super().update(request, *args, **kwargs) provider = Provider.objects.get(id=request.data["id"]) if request.data["is_frozen"]: """ Delete all tokens and sessions for users linked to provider """ users = [ user for user in list([provider.user]) + list(provider.team.all()) if user ] Token.objects.filter(user__in=users).delete() for user in users: user_sessions = [] all_sessions = Session.objects.filter( expire_date__gte=timezone.now()) for session in all_sessions: session_data = session.get_decoded() if str(user.pk) == session_data.get('_auth_user_id'): user_sessions.append(session.pk) Session.objects.filter(pk__in=user_sessions).delete() return response @list_route(methods=['get'], permission_classes=[IsAuthenticated]) def my_providers(self, request): filtered = self.filter_queryset(self.get_queryset()) my = filtered.filter(user=request.user) | request.user.providers.all() return Response([ self.get_serializer_class()(a, context={ 'request': request }).data for a in my ]) @list_route(methods=['post'], permission_classes=[AllowAny]) def create_provider(self, request, *args, **kwargs): """ Customized "create provider" API call. This is distinct from the built-in 'POST to the list URL' call because we need it to work for users who are not authenticated (otherwise, they can't register). Expected data is basically the same as for creating a provider, except that in place of the 'user' field, there should be an 'email' and 'password' field. They'll be used to create a new user, send them an activation email, and create a provider using that user. """ with atomic( ): # If we throw an exception anywhere in here, rollback all changes serializer = CreateProviderSerializer(data=request.data) serializer.is_valid(raise_exception=True) # Create User user = get_user_model().objects.create_user( email=request.data['email'], password=request.data['password'], is_active=False) provider_group, _ = Group.objects.get_or_create(name='Providers') user.groups.add(provider_group) # Create Provider data = dict(request.data, user=user.get_api_url()) serializer = ProviderSerializer(data=data, context={'request': request}) serializer.is_valid(raise_exception=True) serializer.save() # returns provider if we need it headers = self.get_success_headers(serializer.data) # If we got here without blowing up, send the user's activation email user.send_activation_email(request.site, request, data['base_activation_link']) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) @list_route(methods=['post'], permission_classes=[AllowAny]) def claim_service(self, request, *args, **kwargs): try: with atomic(): if request.user.groups.filter(name='Providers').exists(): user = request.user else: try: user = get_user_model().objects.create_user( email=request.data['email'], password=get_user_model( ).objects.make_random_password(), is_active=False) except DjangoValidationError: raise DjangoValidationError( 'User with this email exists') provider_group, _ = Group.objects.get_or_create( name='Providers') user.groups.add(provider_group) # Create or update Provider data = dict(request.data, user=user.get_api_url()) if hasattr(user, 'provider'): serializer = ProviderSerializer( user.provider, data=data, context={'request': request}) else: serializer = ProviderSerializer( data=data, context={'request': request}) try: serializer.is_valid(raise_exception=True) except DRFValidationError: raise DjangoValidationError( 'All fields needs to be filled.') serializer.save() headers = self.get_success_headers(serializer.data) service = Service.objects.get(id=request.data['service_id']) user.send_activation_email_to_staff(request, service, serializer.instance) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) except DjangoValidationError as ex: return Response('; '.join(ex.messages), status=status.HTTP_400_BAD_REQUEST) except Exception as ex: logger.error( 'There was an error during claim', exc_info=True, ) return Response(str(ex), status=status.HTTP_400_BAD_REQUEST) @detail_route(methods=['get'], permission_classes=[permissions.DjangoObjectPermissions]) def export_services(self, request, pk=None, *args, **kwargs): obj = self.get_queryset().filter(pk=pk).first() services = obj.services.all() book = openpyxl.Workbook() sheet = book.active provider_services = [] for s in services: s = serializers_v2.ServiceExcelSerializer(s).data provider_services.append(s) headers = list(serializers_v2.ServiceExcelSerializer.FIELD_MAP.keys()) human_headers = list( serializers_v2.ServiceExcelSerializer.FIELD_MAP.values()) for col in range(0, len(human_headers)): sheet.cell(column=col + 1, row=1).value = human_headers[col] for row in range(0, len(provider_services)): for col in range(0, len(headers)): sheet.cell(column=col + 1, row=row + 2).value = provider_services[row][headers[col]] book_data = BytesIO() book.save(book_data) book_data.seek(0) return Response( { "data": base64.b64encode(book_data.read()), "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }, content_type="application/json") @list_route(methods=['get'], permission_classes=[AllowAny]) def export_services_bulk(self, request, pk=None, *args, **kwargs): book = openpyxl.Workbook() sheet = book.active t1 = time.time() human_headers = tuple( serializers_v2.ServiceExcelSerializer.FIELD_MAP.values()) sheet.append(human_headers) services_list = Service.objects.prefetch_related( 'types', 'confirmation_logs', 'contact_information').all() services_bulk = serializers_v2.ServiceExcelSerializer(services_list, many=True).data for row in services_bulk: sheet.append(tuple(row.values())) book_data = BytesIO() book.save(book_data) book_data.seek(0) t2 = time.time() print('time consumed: ', t2 - t1) return HttpResponse( save_virtual_workbook(book), content_type= 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) @detail_route(methods=['GET']) def impersonate_provider(self, request, pk): request.session['selected-provider'] = pk return Response({}) @detail_route(methods=['GET']) def stop_impersonate_provider(self, request, pk): if 'selected-provider' in request.session: del request.session['selected-provider'] request.session.modified = True logger.error('**** STOP IMPERSONATING') return Response({}) @detail_route(methods=['post'], permission_classes=[permissions.DjangoObjectPermissions], parser_classes=[parsers.MultiPartParser]) def import_services(self, request, pk=None, *args, **kwargs): obj = self.get_queryset().filter(pk=pk).first() file = request.data['file'] with tempfile.NamedTemporaryFile(suffix='.xlsx') as nf: nf.delete = True with open(nf.name, 'w+') as fp: fp.write(str(file.read())) book = openpyxl.load_workbook(file) sheet = book.get_active_sheet() reversed_field_map = dict([ (d[1], d[0]) for d in serializers_v2.ServiceExcelSerializer.FIELD_MAP.items() ]) headers = [ sheet.cell(row=1, column=c).value for c in range(1, sheet.max_column + 1) ] headers = [ reversed_field_map[h] if h in reversed_field_map else h for h in headers ] rows = [[ sheet.cell(row=r, column=c).value for c in range(1, sheet.max_column + 1) ] for r in range(2, sheet.max_row + 1)] rows_to_use = [dict(zip(headers, r)) for r in rows] errors = {} try: with atomic() as t: for a in rows_to_use: for f in [ "name_{}".format(k) for k, v in settings.LANGUAGES ]: if f not in a or not a[f]: a[f] = '' for f in [ "description_{}".format(k) for k, v in settings.LANGUAGES ]: if f not in a or not a[f]: a[f] = '' for f in [ "address_{}".format(k) for k, v in settings.LANGUAGES ]: if f not in a or not a[f]: a[f] = '' serialized = serializers_v2.ServiceExcelSerializer( data=a, partial=True) if serialized.is_valid(): if 'delete' in a and a['delete']: service = Service.objects.get(id=a['id']) service.delete() continue if a['id']: service = Service.objects.get(id=a['id']) else: region = a['region'] region = GeographicRegion.objects.filter( id=region).first() serialized.validated_data.pop('region', '') service = Service(region=region, provider=obj, **serialized.validated_data) service.save() if 'location' in a and a['location']: location = Point(*reversed([ float(b.strip()) for b in a['location'].split(',') ]), srid=4326) else: location = service.location if 'location' in serialized.validated_data: del serialized.validated_data['location'] Service.objects.filter(id=service.id).update( location=location, **serialized.validated_data) else: errors = serialized.errors raise IntegrityError return Response(None, status=204) except IntegrityError: return Response(errors, status=400)
def get_search_fields(self): if 'service-management' in self.request.get_full_path(): return generate_translated_fields('name', False) \ + generate_translated_fields('type__name', False) \ + ['region__name'] \ + ['status'] else: return generate_translated_fields('additional_info', False) \ + ['cost_of_service'] \ + generate_translated_fields('description', False) \ + generate_translated_fields('name', False) \ + generate_translated_fields('type__comments', False) \ + generate_translated_fields('type__name', False) \ + generate_translated_fields('provider__description', False) \ + generate_translated_fields('provider__focal_point_name', False) \ + ['provider__focal_point_phone_number'] \ + generate_translated_fields('provider__address', False) \ + generate_translated_fields('provider__name', False) \ + generate_translated_fields('provider__type__name', False) \ + ['provider__phone_number', 'provider__website', 'provider__user__email'] \ + generate_translated_fields('selection_criteria__text', False) \ + ['region__slug', 'tags__name']
def create(cls, **kwargs): for field in getattr(cls, '_translatable_fields', []): for translated_field in generate_translated_fields(field, False): cls._meta.declarations[ translated_field] = factory.fuzzy.FuzzyText() return super().create(**kwargs)
class ProviderTypeAdmin(admin.ModelAdmin): list_display = generate_translated_fields('name', False) list_display_links = list_display
class ServiceTypeAdmin(admin.ModelAdmin): list_display = ['number'] + generate_translated_fields('name', False) \ + generate_translated_fields('comments', False)
class ServiceAreaAdmin(VersionAdmin): list_display = ['geographic_region', 'pk', 'parent' ] + generate_translated_fields('name', False)
class SelectionCriterionAdmin(admin.ModelAdmin): list_display = ['service'] + generate_translated_fields('text', False)
class ServiceAdmin(AdminImageMixin, VersionAdmin): class Media: css = {"all": ("css/service-admin.css", )} form = ServiceAdminForm actions = ['approve', 'reject'] fieldsets = ( (None, { 'fields': [ 'provider', ('status', 'types'), tuple(generate_translated_fields('name', False)), 'region', 'cost_of_service', 'update_of', 'phone_number', ], }), (_('Address'), { 'classes': ('collapse', ), 'fields': generate_translated_fields('address', False) }), (_('Description and Additional Information'), { 'classes': ('collapse', ), 'fields': generate_translated_fields('description', False) + generate_translated_fields('additional_info', False) }), (_('Hours (all times in time zone {timezone})').format( timezone=settings.TIME_ZONE), { 'classes': ('collapse', ), 'fields': [ ( 'sunday_open', 'sunday_close', ), ( 'monday_open', 'monday_close', ), ( 'tuesday_open', 'tuesday_close', ), ( 'wednesday_open', 'wednesday_close', ), ( 'thursday_open', 'thursday_close', ), ( 'friday_open', 'friday_close', ), ( 'saturday_open', 'saturday_close', ), ] }), (_('Location'), { 'fields': ['location'], }), (_('Image'), { 'fields': [ 'image', ] }), ) inlines = [SelectionCriterionInlineAdmin] list_display = generate_translated_fields('name', False) + \ ['provider', 'get_types', 'status', 'region', 'show_image'] list_display_links = generate_translated_fields( 'name', False) + ['provider', 'region'] list_filter = ['status', 'type'] readonly_fields = ['status'] def approve(self, request, queryset): # All must be in DRAFT status if queryset.exclude(status=Service.STATUS_DRAFT).exists(): self.message_user( request, _("Only services in draft status may be approved"), messages.ERROR) return any_approved = False for service in queryset: try: service.staff_approve(request.user) except ValidationError as e: msg = _("Unable to approve service '{name}': {error}.") msg = msg.format(name=service.name, error=validation_error_as_text(e)) messages.error(request, msg) else: any_approved = True if any_approved: self.message_user(request, _("Services have been approved")) approve.short_description = _("Approve new or changed service") def reject(self, request, queryset): # All must be in DRAFT status if queryset.exclude(status=Service.STATUS_DRAFT).exists(): self.message_user( request, _("Only services in draft status may be rejected"), messages.ERROR) return any_rejected = False for service in queryset: try: service.staff_reject(request.user) except ValidationError as e: msg = _("Unable to reject service '{name}': {error}.") msg = msg.format(name=service.name, error=validation_error_as_text(e)) messages.error(request, msg) else: any_rejected = True if any_rejected: self.message_user(request, _("Services have been rejected")) reject.short_description = _("Reject new or changed service") def response_change(self, request, obj): """ Determines the HttpResponse for the change_view stage. """ if '_approve' in request.POST: try: obj.staff_approve(request.user) except ValidationError as e: msg = _("Unable to approve service '{name}': {error}.") msg = msg.format(name=obj.name, error=validation_error_as_text(e)) self.message_user(request, msg, messages.ERROR) redirect_url = add_preserved_filters( { 'preserved_filters': self.get_preserved_filters(request), 'opts': self.model._meta }, request.path) return HttpResponseRedirect(redirect_url) else: msg = _('The service was approved successfully.') self.message_user(request, msg, messages.SUCCESS) elif '_reject' in request.POST: try: obj.staff_reject(request.user) except ValidationError as e: msg = _("Unable to reject service '{name}': {error}.") msg = msg.format(name=obj.name, error=validation_error_as_text(e)) self.message_user(request, msg, messages.ERROR) redirect_url = add_preserved_filters( { 'preserved_filters': self.get_preserved_filters(request), 'opts': self.model._meta }, request.path) return HttpResponseRedirect(redirect_url) else: msg = _('The service was rejected successfully.') self.message_user(request, msg, messages.INFO) return super().response_change(request, obj) def show_image(self, obj): """Create a thumbnail of this image to show in the admin list.""" thumbnail_url = obj.get_thumbnail_url() if thumbnail_url: return '<img src="{}" />'.format(thumbnail_url) return _("no image") def get_types(self, obj): return ','.join([t.name_en for t in obj.types.all()]) show_image.allow_tags = True show_image.short_description = "Image"