Python django.db.transaction.atomic() Examples

The following are 30 code examples for showing how to use django.db.transaction.atomic(). These examples are extracted from open source projects. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.

You may check out the related API usage on the sidebar.

You may also want to check out all available functions/classes of the module django.db.transaction , or try the search function .

Example 1
Project: django-anonymizer   Author: BetterWorks   File: base.py    License: MIT License 6 votes vote down vote up
def _run(anonymizer, objs):
    values = {}
    replacer_attr = tuple(r[0] for r in anonymizer.replacers)
    for obj in objs.iterator():
        retval = anonymizer.alter_object(obj)
        if retval is False:
            continue

        values[obj.pk] = {attname: getattr(obj, attname) for attname in replacer_attr}

    query = anonymizer.create_query(replacer_attr)
    query_args = anonymizer.create_query_args(values, replacer_attr)

    with transaction.atomic():
        with connection.cursor() as cursor:
            if connection.vendor == 'postgresql':
                cursor.execute('SET CONSTRAINTS ALL DEFERRED')
            cursor.executemany(query, query_args) 
Example 2
Project: open-synthesis   Author: twschiller   File: evidence.py    License: GNU General Public License v3.0 6 votes vote down vote up
def toggle_source_tag(request, evidence_id, source_id):
    """Toggle source tag for the given source and redirect to the evidence detail page for the associated evidence."""
    # May want to put in a sanity check here that source_id actually corresponds to evidence_id
    # Inefficient to have to do the DB lookup before making a modification. May want to have the client pass in
    # whether or not they're adding/removing the tag
    if request.method == 'POST':
        with transaction.atomic():
            source = get_object_or_404(EvidenceSource, pk=source_id)
            tag = EvidenceSourceTag.objects.get(tag_name=request.POST['tag'])
            user_tag = AnalystSourceTag.objects.filter(source=source, tagger=request.user, tag=tag)
            if user_tag.count() > 0:
                user_tag.delete()
                messages.success(request, _('Removed "{name}" tag from source.').format(name=tag.tag_name))
            else:
                AnalystSourceTag.objects.create(source=source, tagger=request.user, tag=tag)
                messages.success(request, _('Added "{name}" tag to source.').format(name=tag.tag_name))
            return HttpResponseRedirect(reverse('openach:evidence_detail', args=(evidence_id,)))
    else:
        # Redirect to the form where the user can toggle a source tag
        return HttpResponseRedirect(reverse('openach:evidence_detail', args=(evidence_id,))) 
Example 3
Project: open-synthesis   Author: twschiller   File: teams.py    License: GNU General Public License v3.0 6 votes vote down vote up
def create_team(request):
    """Return a team creation view, or handle the form submission."""
    if request.method == 'POST':
        form = TeamCreateForm(request.POST)
        if form.is_valid():
            with transaction.atomic():
                team = form.save(commit=False)
                team.owner = request.user
                team.creator = request.user
                team.save()
                team.members.add(request.user)
                team.save()
            return HttpResponseRedirect(reverse('openach:view_team', args=(team.id,)))
    else:
        form = TeamCreateForm()
    return render(request, 'teams/create_team.html', {'form': form}) 
Example 4
Project: coursys   Author: sfu-fas   File: models.py    License: GNU General Public License v3.0 6 votes vote down vote up
def create_reminder_on(self, date, start_date, end_date):
        if start_date > date or date > end_date:
            # not timely, so ignore
            return

        if self.reminder_type == 'ROLE':
            roles = Role.objects_fresh.filter(unit=self.unit, role=self.role).select_related('person')
            recipients = [r.person for r in roles]
        elif self.reminder_type in ['PERS', 'INST']:
            recipients = [self.person]
        else:
            raise ValueError()

        for recip in recipients:
            ident = '%s_%s_%s' % (self.slug, recip.userid_or_emplid(), date.isoformat())
            # ident length: slug (50) + userid/emplid (9) + ISO date (10) + _ (2) <= 71
            rm = ReminderMessage(reminder=self, sent=False, date=date, person=recip, ident=ident)
            with transaction.atomic():
                try:
                    rm.save()
                except IntegrityError:
                    # already been created because we got IntegrityError on rm.ident
                    pass 
Example 5
Project: coursys   Author: sfu-fas   File: models.py    License: GNU General Public License v3.0 6 votes vote down vote up
def deduplicate(cls, start_date=None, end_date=None, dry_run=False):
        """
        Remove any EnrolmentHistory objects that aren't adding any new information.
        """
        all_ehs = EnrolmentHistory.objects.order_by('offering', 'date')
        if start_date:
            all_ehs = all_ehs.filter(date__gte=start_date)
        if end_date:
            all_ehs = all_ehs.filter(date__lte=end_date)

        for off_id, ehs in itertools.groupby(all_ehs, key=lambda eh: eh.offering_id):
            # iterate through EnrolmentHistory for this offering and purge any "same as yesterday" entries
            with transaction.atomic():
                current = next(ehs)
                for eh in ehs:
                    if current.is_dup(eh):
                        if not dry_run:
                            eh.delete()
                        else:
                            print('delete', eh)
                    else:
                        current = eh 
Example 6
Project: coursys   Author: sfu-fas   File: models.py    License: GNU General Public License v3.0 6 votes vote down vote up
def safely_delete(self):
        """
        Do the actions to safely "delete" the activity.
        """
        with transaction.atomic():
            # mangle name and short-name so instructors can delete and replace
            i = 1
            while True:
                suffix = "__%04i" % (i)
                existing = Activity.objects.filter(offering=self.offering, name=self.name+suffix).count() \
                        + Activity.objects.filter(offering=self.offering, short_name=self.short_name+suffix).count()
                if existing == 0:
                    break
                i += 1

            # update the activity
            self.deleted = True
            # Truncate the names if we need to since we are adding a 6 character suffix 
            self.name = self.name[:24] + suffix
            self.short_name = self.short_name[:9] + suffix
            self.slug = None
            self.save() 
Example 7
Project: arches   Author: archesproject   File: graph.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def post(self, request, cardid=None):
        data = JSONDeserializer().deserialize(request.body)
        if self.action == "update_card":
            if data:
                card = Card(data)
                card.save()
                return JSONResponse(card)

        if self.action == "reorder_cards":
            if "cards" in data and len(data["cards"]) > 0:
                with transaction.atomic():
                    for card_data in data["cards"]:
                        card = models.CardModel.objects.get(pk=card_data["id"])
                        card.sortorder = card_data["sortorder"]
                        card.save()
                return JSONResponse(data["cards"])

        return HttpResponseNotFound() 
Example 8
Project: arches   Author: archesproject   File: graph.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def post(self, request, graphid):
        data = JSONDeserializer().deserialize(request.body)
        self.graph = Graph.objects.get(graphid=graphid)
        with transaction.atomic():
            for item in data:
                functionXgraph, created = models.FunctionXGraph.objects.update_or_create(
                    pk=item["id"], defaults={"function_id": item["function_id"], "graph_id": graphid, "config": item["config"]}
                )
                item["id"] = functionXgraph.pk

                # run post function save hook
                func = functionXgraph.function.get_class_module()()
                try:
                    func.after_function_save(functionXgraph, request)
                except NotImplementedError:
                    pass

        return JSONResponse(data) 
Example 9
Project: arches   Author: archesproject   File: graph.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def post(self, request):
        nodegroupid = None
        try:
            nodegroupid = uuid.UUID(str(request.POST.get("nodegroupid")))
        except Exception as e:
            print(e)
        if self.action == "exportable" and nodegroupid is not None:
            exportable = json.loads(request.POST.get("exportable"))

            nodegroup = models.NodeGroup.objects.select_for_update().filter(nodegroupid=nodegroupid)
            with transaction.atomic():
                for ng in nodegroup:
                    ng.exportable = exportable
                    ng.save()

            return JSONResponse({"nodegroup": nodegroupid, "status": "success"})

        return HttpResponseNotFound() 
Example 10
Project: arches   Author: archesproject   File: mobile_survey.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def delete(self, request):
        mobile_survey_id = None
        try:
            mobile_survey_id = JSONDeserializer().deserialize(request.body)["id"]
        except Exception as e:
            logger.exception(e)

        try:
            connection_error = False
            with transaction.atomic():
                if mobile_survey_id is not None:
                    ret = MobileSurvey.objects.get(pk=mobile_survey_id)
                    ret.delete()
                    return JSONResponse({"success": True})
        except Exception as e:
            if connection_error is False:
                error_title = _("Unable to delete collector project")
                if "strerror" in e and e.strerror == "Connection refused" or "Connection refused" in e:
                    error_message = _("Unable to connect to CouchDB")
                else:
                    error_message = e.message
                connection_error = JSONResponse({"success": False, "message": error_message, "title": error_title}, status=500)
            return connection_error

        return HttpResponseNotFound() 
Example 11
Project: arches   Author: archesproject   File: mobile_survey.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def delete(self, request, surveyid):
        try:
            connection_error = False
            with transaction.atomic():
                if surveyid is not None:
                    ret = MobileSurvey.objects.get(pk=surveyid)
                    ret.delete()
                    return JSONResponse({"success": True})
        except Exception as e:
            if connection_error is False:
                error_title = _("Unable to delete survey")
                if "strerror" in e and e.strerror == "Connection refused" or "Connection refused" in e:
                    error_message = _("Unable to connect to CouchDB. Please confirm that CouchDB is running")
                else:
                    error_message = e.message
                connection_error = JSONErrorResponse(error_title, error_message)
            return connection_error

        return HttpResponseNotFound() 
Example 12
Project: arches   Author: archesproject   File: concept.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def concept_value(request):
    if request.method == "DELETE":
        data = JSONDeserializer().deserialize(request.body)

        if data:
            with transaction.atomic():
                value = ConceptValue(data)
                value.delete_index()
                value.delete()
                return JSONResponse(value)
    if request.method == "GET":
        valueid = request.GET.get("valueid")
        value = models.Value.objects.get(pk=valueid)
        return JSONResponse(value)

    return HttpResponseNotFound 
Example 13
Project: arches   Author: archesproject   File: packages.py    License: GNU Affero General Public License v3.0 6 votes vote down vote up
def add_mapbox_layer(
        self, layer_name=False, mapbox_json_path=False, layer_icon="fa fa-globe", is_basemap=False,
    ):
        if layer_name is not False and mapbox_json_path is not False:
            with open(mapbox_json_path) as data_file:
                data = json.load(data_file)
                with transaction.atomic():
                    for layer in data["layers"]:
                        if "source" in layer:
                            layer["source"] = layer["source"] + "-" + layer_name
                    for source_name, source_dict in data["sources"].items():
                        map_source = models.MapSource.objects.get_or_create(name=source_name + "-" + layer_name, source=source_dict)
                    map_layer = models.MapLayer(
                        name=layer_name, layerdefinitions=data["layers"], isoverlay=(not is_basemap), icon=layer_icon
                    )
                    try:
                        map_layer.save()
                    except IntegrityError as e:
                        print("Cannot save layer: {0} already exists".format(layer_name)) 
Example 14
Project: heltour   Author: cyanfish   File: models.py    License: MIT License 6 votes vote down vote up
def perform_withdrawal(self):
        with transaction.atomic():
            # Set the SeasonPlayer as inactive
            sp, _ = SeasonPlayer.objects.get_or_create(season=self.round.season, player=self.player)
            sp.is_active = False
            sp.save()

            # Delete pairings and give opponents byes
            for pairing in self.round.loneplayerpairing_set.filter(white=self.player):
                PlayerBye.objects.create(round=self.round, player=pairing.black,
                                         type='full-point-pairing-bye')
                pairing.delete()
            for pairing in self.round.loneplayerpairing_set.filter(black=self.player):
                PlayerBye.objects.create(round=self.round, player=pairing.white,
                                         type='full-point-pairing-bye')
                pairing.delete() 
Example 15
Project: resolwe   Author: genialis   File: 0028_add_data_location.py    License: Apache License 2.0 6 votes vote down vote up
def set_data_location(apps, schema_editor):
    """Create DataLocation for each Data."""
    Data = apps.get_model("flow", "Data")
    DataLocation = apps.get_model("flow", "DataLocation")

    for data in Data.objects.all():
        if os.path.isdir(
            os.path.join(settings.FLOW_EXECUTOR["DATA_DIR"], str(data.id))
        ):
            with transaction.atomic():
                # Manually set DataLocation id to preserve data directory.
                data_location = DataLocation.objects.create(
                    id=data.id, subpath=str(data.id)
                )
                data_location.data.add(data)

    # Increment DataLocation id's sequence
    if DataLocation.objects.exists():
        max_id = DataLocation.objects.order_by("id").last().id
        with connection.cursor() as cursor:
            cursor.execute(
                "ALTER SEQUENCE flow_datalocation_id_seq RESTART WITH {};".format(
                    max_id + 1
                )
            ) 
Example 16
Project: resolwe   Author: genialis   File: base.py    License: Apache License 2.0 6 votes vote down vote up
def delete_chunked(queryset, chunk_size=500):
    """Chunked delete, which should be used if deleting many objects.

    The reason why this method is needed is that deleting a lot of Data objects
    requires Django to fetch all of them into memory (fast path is not used) and
    this causes huge memory usage (and possibly OOM).

    :param chunk_size: Optional chunk size
    """
    while True:
        # Discover primary key to limit the current chunk. This is required because delete
        # cannot be called on a sliced queryset due to ordering requirement.
        with transaction.atomic():
            # Get offset of last item (needed because it may be less than the chunk size).
            offset = queryset.order_by("pk")[:chunk_size].count()
            if not offset:
                break

            # Fetch primary key of last item and use it to delete the chunk.
            last_instance = queryset.order_by("pk")[offset - 1]
            queryset.filter(pk__lte=last_instance.pk).delete() 
Example 17
Project: resolwe   Author: genialis   File: base.py    License: Apache License 2.0 6 votes vote down vote up
def save(self, *args, **kwargs):
        """Save the model."""
        name_max_len = self._meta.get_field("name").max_length
        if len(self.name) > name_max_len:
            self.name = self.name[: (name_max_len - 3)] + "..."

        for _ in range(MAX_SLUG_RETRIES):
            try:
                # Attempt to save the model. It may fail due to slug conflict.
                with transaction.atomic():
                    super().save(*args, **kwargs)
                    break
            except IntegrityError as error:
                # Retry in case of slug conflicts.
                if "{}_slug".format(self._meta.db_table) in error.args[0]:
                    self.slug = None
                    continue

                raise
        else:
            raise IntegrityError(
                "Maximum number of retries exceeded during slug generation"
            ) 
Example 18
Project: resolwe   Author: genialis   File: cleanup.py    License: Apache License 2.0 6 votes vote down vote up
def process(self, file_storage_id: Optional[int] = None):
        """Process objects to clean.

        When file_storage is not None process only that object.
        """
        logger.debug("Starting cleanup manager run")
        qset = FileStorage.objects.all()
        if file_storage_id is not None:
            qset = qset.filter(pk=file_storage_id)
        for file_storage in qset.filter(data__isnull=True).iterator():
            # Set applicable storage locations to deleting.
            StorageLocation.all_objects.unreferenced_locations().filter(
                file_storage=file_storage
            ).update(status=StorageLocation.STATUS_DELETING)
            with transaction.atomic():
                q_set = FileStorage.objects.filter(
                    id=file_storage.id
                ).select_for_update(skip_locked=True)
                # The FileStorage object is locked or deleted, skip processing.
                if not q_set.exists():
                    continue
                self._process_file_storage(q_set.first())

        logger.debug("Finished cleanup manager run") 
Example 19
Project: pinax-documents   Author: pinax   File: views.py    License: MIT License 5 votes vote down vote up
def form_valid(self, form):
        with transaction.atomic():
            kwargs = self.get_create_kwargs(form)
            self.object = self.create_document(**kwargs)
            hookset.document_created_message(self.request, self.object)
            bytes = form.cleaned_data["file"].size
            self.increase_usage(bytes)
            return HttpResponseRedirect(self.get_success_url()) 
Example 20
Project: raveberry   Author: raveberry   File: music_provider.py    License: GNU Lesser General Public License v3.0 5 votes vote down vote up
def persist(self, request_ip: str, archive: bool = True) -> None:
        metadata = self.get_metadata()

        # Increase counter of song/playlist
        with transaction.atomic():
            queryset = ArchivedSong.objects.filter(url=metadata["external_url"])
            if queryset.count() == 0:
                initial_counter = 1 if archive else 0
                archived_song = ArchivedSong.objects.create(
                    url=metadata["external_url"],
                    artist=metadata["artist"],
                    title=metadata["title"],
                    counter=initial_counter,
                )
            else:
                if archive:
                    queryset.update(counter=F("counter") + 1)
                archived_song = queryset.get()

            if archive:
                ArchivedQuery.objects.get_or_create(
                    song=archived_song, query=self.query
                )

        if self.musiq.base.settings.basic.logging_enabled and request_ip:
            RequestLog.objects.create(song=archived_song, address=request_ip) 
Example 21
Project: raveberry   Author: raveberry   File: music_provider.py    License: GNU Lesser General Public License v3.0 5 votes vote down vote up
def persist(self, request_ip: str, archive: bool = True) -> None:
        if self.is_radio():
            return

        assert self.id
        if self.title is None:
            logging.warning("Persisting a playlist with no title (id %s)", self.id)
            self.title = ""

        with transaction.atomic():
            queryset = ArchivedPlaylist.objects.filter(list_id=self.id)
            if queryset.count() == 0:
                initial_counter = 1 if archive else 0
                archived_playlist = ArchivedPlaylist.objects.create(
                    list_id=self.id, title=self.title, counter=initial_counter
                )
                for index, url in enumerate(self.urls):
                    PlaylistEntry.objects.create(
                        playlist=archived_playlist, index=index, url=url,
                    )
            else:
                if archive:
                    queryset.update(counter=F("counter") + 1)
                archived_playlist = queryset.get()

        if archive:
            ArchivedPlaylistQuery.objects.get_or_create(
                playlist=archived_playlist, query=self.query
            )

        if self.musiq.base.settings.basic.logging_enabled and request_ip:
            RequestLog.objects.create(playlist=archived_playlist, address=request_ip) 
Example 22
Project: raveberry   Author: raveberry   File: controller.py    License: GNU Lesser General Public License v3.0 5 votes vote down vote up
def remove_all(self, request: WSGIRequest) -> HttpResponse:
        """Empties the queue. Only admin is permitted to do this."""
        if not self.musiq.base.user_manager.is_admin(request.user):
            return HttpResponseForbidden()
        with self.playback.mopidy_command() as allowed:
            if allowed:
                with transaction.atomic():
                    count = self.playback.queue.count()
                    self.playback.queue.all().delete()
                for _ in range(count):
                    self.playback.queue_semaphore.acquire(blocking=False)
        return HttpResponse() 
Example 23
Project: raveberry   Author: raveberry   File: base.py    License: GNU Lesser General Public License v3.0 5 votes vote down vote up
def _increment_counter(self) -> int:
        with transaction.atomic():
            counter = models.Counter.objects.get_or_create(id=1, defaults={"value": 0})[
                0
            ]
            counter.value += 1
            counter.save()
        self.update_state()
        return counter.value 
Example 24
Project: raveberry   Author: raveberry   File: pad.py    License: GNU Lesser General Public License v3.0 5 votes vote down vote up
def submit(self, request: WSGIRequest) -> HttpResponse:
        """Stores a new version of the pad content.
        Makes sure that no conflicts occur."""
        version = request.POST.get("version")
        content = request.POST.get("content")
        if version is None or version == "" or content is None:
            return HttpResponseBadRequest("No version or content supplied")
        try:
            iversion = int(version)
        except ValueError:
            return HttpResponseBadRequest("version is not a number")

        with transaction.atomic():
            pad = models.Pad.objects.get(id=1)
            current_version = pad.version
            if current_version == iversion:
                version_valid = True
                pad.version += 1
                pad.content = content
                pad.save()
            else:
                version_valid = False

        if not version_valid:
            return HttpResponseBadRequest(
                "The content was changed in the meantime, please reload"
            )
        self.update_state()
        return HttpResponse("Updated Pad") 
Example 25
Project: normandy   Author: mozilla   File: test_api.py    License: Mozilla Public License 2.0 5 votes vote down vote up
def test_cannot_create_extension_duplicate_filename(self, api_client, storage):
        xpi = WebExtensionFileFactory()
        ExtensionFactory(xpi__from_func=xpi.open)
        with transaction.atomic():
            res = self._upload_extension(api_client, xpi.path)
        assert res.status_code == 400
        assert res.data == {"xpi": "An extension with this filename already exists."}
        assert Extension.objects.count() == 1 
Example 26
Project: normandy   Author: mozilla   File: test_api.py    License: Mozilla Public License 2.0 5 votes vote down vote up
def test_cannot_update_extension_duplicate_filename(self, api_client, storage):
        xpi1 = WebExtensionFileFactory()
        ExtensionFactory(xpi__from_func=xpi1.open)

        xpi2 = WebExtensionFileFactory()
        e = ExtensionFactory(xpi__from_func=xpi2.open)

        with transaction.atomic():
            res = self._update_extension(api_client, e.id, xpi1.path)
        assert res.status_code == 400
        assert res.data == {"xpi": "An extension with this filename already exists."} 
Example 27
Project: normandy   Author: mozilla   File: test_models.py    License: Mozilla Public License 2.0 5 votes vote down vote up
def test_no_duplicate_files(self, storage):
        xpi = WebExtensionFileFactory()
        ExtensionFactory(xpi__from_func=xpi.open)
        with transaction.atomic(), pytest.raises(FileExistsError):
            ExtensionFactory(xpi__from_func=xpi.open)
        assert Extension.objects.count() == 1 
Example 28
Project: donation-tracker   Author: GamesDoneQuick   File: viewutil.py    License: Apache License 2.0 5 votes vote down vote up
def autocreate_donor_user(donor):
    AuthUser = get_user_model()

    if not donor.user:
        with transaction.atomic():
            try:
                linkUser = AuthUser.objects.get(email=donor.email)
            except AuthUser.MultipleObjectsReturned:
                message = 'Multiple users found for email {0}, when trying to mail donor {1} for prizes'.format(
                    donor.email, donor.id
                )
                tracker_log('prize', message)
                raise Exception(message)
            except AuthUser.DoesNotExist:
                targetUsername = donor.email
                if donor.alias and not AuthUser.objects.filter(username=donor.alias):
                    targetUsername = donor.alias
                linkUser = AuthUser.objects.create(
                    username=targetUsername,
                    email=donor.email,
                    first_name=donor.firstname,
                    last_name=donor.lastname,
                    is_active=False,
                )
            donor.user = linkUser
            donor.save()

    return donor.user 
Example 29
Project: open-synthesis   Author: twschiller   File: evidence.py    License: GNU General Public License v3.0 5 votes vote down vote up
def add_evidence(request, board_id):
    """Return a view of adding evidence (with a source), or handle the form submission."""
    board = get_object_or_404(Board, pk=board_id)

    if 'add_elements' not in board.permissions.for_user(request.user):
        raise PermissionDenied()

    require_source = getattr(settings, 'EVIDENCE_REQUIRE_SOURCE', True)

    if request.method == 'POST':
        evidence_form = EvidenceForm(request.POST)
        source_form = EvidenceSourceForm(request.POST, require=require_source)
        if evidence_form.is_valid() and source_form.is_valid():
            with transaction.atomic():
                evidence = evidence_form.save(commit=False)
                evidence.board = board
                evidence.creator = request.user
                evidence.save()

                if source_form.cleaned_data.get('source_url'):
                    source = source_form.save(commit=False)
                    source.evidence = evidence
                    source.uploader = request.user
                    source.save()
                    fetch_source_metadata.delay(source.id)

                BoardFollower.objects.update_or_create(board=board, user=request.user, defaults={
                    'is_contributor': True,
                })
            notify_add(board, actor=request.user, action_object=evidence)
            return HttpResponseRedirect(reverse('openach:detail', args=(board.id,)))
    else:
        evidence_form = EvidenceForm()
        source_form = EvidenceSourceForm(require=require_source, initial={'corroborating': True})

    context = {
        'board': board,
        'evidence_form': evidence_form,
        'source_form': source_form,
    }
    return render(request, 'boards/add_evidence.html', context) 
Example 30
Project: open-synthesis   Author: twschiller   File: boards.py    License: GNU General Public License v3.0 5 votes vote down vote up
def create_board(request):
    """Return a board creation view, or handle the form submission.

    Set default permissions for the new board. Mark board creator as a board follower.
    """
    if request.method == 'POST':
        form = BoardCreateForm(request.POST)
        if form.is_valid():
            with transaction.atomic():
                board = form.save(commit=False)
                board.creator = request.user
                board.pub_date = timezone.now()
                board.save()
                for hypothesis_key in ['hypothesis1', 'hypothesis2']:
                    Hypothesis.objects.create(
                        board=board,
                        hypothesis_text=form.cleaned_data[hypothesis_key],
                        creator=request.user,
                    )
                BoardFollower.objects.update_or_create(board=board, user=request.user, defaults={
                    'is_creator': True,
                })

            return HttpResponseRedirect(reverse('openach:detail', args=(board.id,)))
    else:
        form = BoardCreateForm()
    return render(request, 'boards/create_board.html', {'form': form})