Sites

Sites consist of a name, description, and location. A photo of the physical location is optional, a default image will be provided if no new images is uploaded.

In the previous application, Sites were called Projects, but the functionality is essentially the same.

Data is directly associated with a site, all datasheets must have a one-to-one associate with a Site.

The Site Model

We use Model Managers on most streamwebs models. See Django Model Managers documentation for information on why and how to use managers.

The Site model manager

class SiteManager(models.Manager):
    """
    Manager for the site class - creates a site to be used in tests
    """
    default = 'POINT(-121.3846841 44.0612385)'

    def create_site(self, site_name, location=default,
                    description='', image=None, active=True):

        site = self.create(site_name=site_name, location=location,
                           description=description, image=image, active=active)
        return site

The Site model

@python_2_unicode_compatible
class Site(models.Model):
    """
    The Sites model holds the information of a site, including the geographic
    location as a pair of latitudinal/longitudinal coordinates and an optional
    text description of entry.
    """

    site_name = models.CharField(max_length=250, verbose_name=_('site name'))
    description = models.TextField(blank=True,
                                   verbose_name=_('site description'))
    site_slug = models.SlugField(unique=True, max_length=50, editable=False)

    # Geo Django fields to store a point
    location = models.PointField(default='POINT(-121.3846841 44.0612385)',
                                 verbose_name=_('location'),
                                 validators=[validate_Site_location])
    image = models.ImageField(null=True, blank=True, verbose_name=_('image'),
                              upload_to='site_photos/')
    active = models.BooleanField(default=True)

    created = models.DateTimeField(default=timezone.now)
    modified = models.DateTimeField(default=timezone.now)

    objects = models.Manager()  # default manager
    test_objects = SiteManager()  # custom manager for use in writing tests

    def __str__(self):
        return self.site_name

    def to_dict(self):
        return {
            'site_name': self.site_name,
            'site_slug': self.site_slug,
            'description': self.description,
            'location': {
                'x': self.location.x,
                'y': self.location.y
            },
            'created': _timestamp(self.created),
            'modified': _timestamp(self.modified)
        }

    def save(self, **kwargs):
        'Ensure site_slug is a unique, not-null field'
        if not self.site_slug:
            self.site_slug = origSlug = slugify(self.site_name)[:50]
            # If the generated slug is not unique, append a number
            for i in xrange(1, 99):
                if not Site.objects.filter(site_slug=self.site_slug).exists():
                    break
                # Ensure the slug is never longer than it's field's maxiumum
                self.site_slug = "%s%d" % (origSlug[:50-len(str(i))], i)
        super(Site, self).save()

The Site Form

Sites can be added or edited using the Site form.

The Site form

class SiteForm(forms.ModelForm):
    class Meta:
        model = Site
        widgets = {
            'site_name': forms.TextInput(
                attrs={'class': 'materialize-textarea, validate'}),
            'description': forms.Textarea(
                attrs={'class': 'materialize-textarea'})
        }
        fields = ('site_name', 'description', 'location', 'image')

The Site Views

There are several views relating to Sites. There are the standard views for View, List, Create, Update, and Deactivate. Site views always include a map object displaying the Site location.

sites

In the sites view (list all sites), all available sites are presented as markers on Google map of Oregon. This view also allows searching by name, selecting a site from a pull-down list, and creating a new site. Any registered user may create a new site.

site

In the site view (view an individual site), the site location is displayed on a map along with the photo of the site and all data associated with the site. Data is displayed as a table of datasheets organized by the school which submitted the data.

Additionally, if graphable data is associated with the site (Water Quality, Macroinvertabrates, or Riparian Transect), buttons will appear to view graphs of the sumbitted data.

Finally, links are provided to add data or export data to a CSV file.

update_site

This view renders the Site form and allows the user to change the name, location, image or description.

deactivate_site

The deactivate site view will ‘delete’ the site by setting its active field to False. This can only be done if no data is associated with this site.

Site Views

def any_organization_required(func):
    def wrapper(request, *args, **kwargs):
        if UserProfile.objects.filter(user=request.user).exists():
            user_profile = UserProfile.objects.get(user=request.user)
            if user_profile is None or user_profile.school is None:
                return HttpResponseForbidden(
                    'Your account is not associated with any school.')
            return func(request, *args, **kwargs)
        else:
            return HttpResponseForbidden(
                'Your account is not associated with any school.')
    return wrapper


# Decorator function that requires the user to be a part of the
# same school as the page they are attempting to access.
def organization_required(func):
    def wrapper(request, *args, **kwargs):
        school_data = School.objects.get(id=kwargs['school_id'])

        if not request.user.has_perm('streamwebs.is_super_admin'):
            user_profile = UserProfile.objects.get(user=request.user)
            if user_profile is None:
                return HttpResponseForbidden(
                    'Your account is not associated with any school.')
            if user_profile.school != school_data:
                return HttpResponseForbidden(
                    'Your account is not associated with this school.')
        return func(request, *args, **kwargs)
    return wrapper


# Decorator function that requires the school to be active
def organization_approved(func):
    def wrapper(request, *args, **kwargs):
        school_data = School.objects.get(id=kwargs['school_id'])

        if not school_data.active:
            return HttpResponseRedirect('/schools/%i/' % school_data.id)
        return func(request, *args, **kwargs)
    return wrapper


# Send an email
def send_email(request, subject, template, user, school, from_email,
               recipients):
    if settings.SEND_EMAILS:
        send_mail(
            subject=subject,
            message='',
            html_message=render_to_string(
                template,
                {
                    'protocol': request.scheme,
                    'domain': request.get_host(),
                    'user': user,
                    'school': school
                }),
            from_email=from_email,
            recipient_list=recipients,
            fail_silently=False,
        )


def toDateTime(date, time, period):
    date_time = datetime.datetime.strptime((date + " " + time + " " + period),
                                           '%Y-%m-%d %I:%M %p')
    return date_time


def _timestamp(dt):
    return (dt - datetime.datetime(1970, 1, 1)).total_seconds()


def index(request):
    return render(request, 'streamwebs/index.html', {})


def about(request):
    return render(request, 'streamwebs/about.html', {})


def faq(request):
    return render(request, 'streamwebs/faq.html', {})


def confirm_registration(request):
    return render(request, 'streamwebs/confirm_register.html', {})


@login_required
@permission_required('streamwebs.is_org_admin', raise_exception=True)
def create_site(request):
    created = False
    site_list = Site.objects.filter(active=True)

    if request.method == 'POST':
        if not request.POST._mutable:
            request.POST._mutable = True

        if 'lat' in request.POST and 'lng' in request.POST:
            # convert lat/lng to pointfield object
            point = ("SRID=4326;POINT(%s %s)" %
                     (request.POST['lng'], request.POST['lat']))
            request.POST['location'] = point

        site_form = SiteForm(request.POST, request.FILES)
        if site_form.is_valid():
            site = site_form.save()
            site.save()
            created = True
            messages.success(request,
                             _('You have successfully added a new site.'))
            return redirect(reverse('streamwebs:site',
                            kwargs={'site_slug': site.site_slug}))

    else:
        site_form = SiteForm()

    return render(request, 'streamwebs/create_site.html', {
        'site_form': site_form,
        'created': created,
        'sites': site_list,
        'maps_api': settings.GOOGLE_MAPS_API,
        'map_type': settings.GOOGLE_MAPS_TYPE
        })


def sites(request):
    """ View for streamwebs/sites """
    site_list = Site.objects.filter(active=True).order_by('site_name')
    return render(request, 'streamwebs/sites.html', {
        'sites': site_list,
        'maps_api': settings.GOOGLE_MAPS_API,
        'map_type': settings.GOOGLE_MAPS_TYPE
    })


# view-view for individual specified site
def site(request, site_slug):
    num_elements_page = 10

    """ View an individual site """

    site = Site.objects.filter(active=True).get(site_slug=site_slug)

    datasheets, template_has = get_datasheets(site.id)
    datasheets = add_school_name(datasheets)