def get_all_addable_physical_process_models(): """Get all physical process classes (depositions, measurements; no sample splits) that one can add or edit. Never call this routine from top-level module code because it may cause cyclic imports. :return: Dictionary mapping all physical processes one can to add. Every process class is mapped to a dictionary with three keys, namely ``"url"`` with the url to the “add” view for the process, ``"label"`` with the name of the process (starting lowercase), and ``"type"`` with the process' class name. :rtype: dict mapping class to dict mapping str to unicode """ global all_addable_physical_process_models if all_addable_physical_process_models is None: all_addable_physical_process_models = {} for process_class in utils.get_all_models().values(): if issubclass(process_class, samples.models.PhysicalProcess): url = process_class.get_add_link() if url: all_addable_physical_process_models[process_class] = { "url": url, "label": process_class._meta.verbose_name, "label_plural": process_class._meta.verbose_name_plural, "type": process_class.__name__ } return all_addable_physical_process_models
def get_all_searchable_models(): """Returns all model classes which have a ``get_search_tree_node`` method. :return: all searchable model classes :rtype: frozenset of ``class`` """ global all_searchable_models if not isinstance(all_searchable_models, frozenset): if isinstance(all_searchable_models, set): raise SetLockedException all_searchable_models = set() for model in utils.get_all_models().values(): if hasattr(model, "get_search_tree_node"): try: model.get_search_tree_node() except NotImplementedError: pass except SetLockedException: all_searchable_models.add(model) else: all_searchable_models.add(model) all_searchable_models = frozenset(all_searchable_models) return all_searchable_models
def get_all_addable_physical_process_models(): """Get all physical process classes (depositions, measurements; no sample splits) that one can add or edit. Never call this routine from top-level module code because it may cause cyclic imports. :return: Dictionary mapping all physical processes one can to add. Every process class is mapped to a dictionary with three keys, namely ``"url"`` with the url to the “add” view for the process, ``"label"`` with the name of the process (starting lowercase), and ``"type"`` with the process' class name. :rtype: dict mapping class to dict mapping str to unicode """ global all_addable_physical_process_models if all_addable_physical_process_models is None: all_addable_physical_process_models = {} for process_class in utils.get_all_models().values(): if issubclass(process_class, samples.models.PhysicalProcess): url = process_class.get_add_link() if url: all_addable_physical_process_models[process_class] = { "url": url, "label": process_class._meta.verbose_name, "label_plural": process_class._meta.verbose_name_plural, "type": process_class.__name__} return all_addable_physical_process_models
def show(request, process_name, year_and_month): """View for showing one month of the lab notebook for a particular physical process. In ``urls.py``, you must give the entry for this view the name ``"lab_notebook_<camel_case_process_name>"``. :param request: the current HTTP Request object :param process_name: the class name of the model of the physical process, e.g. ``"LargeAreaDeposition"`` :param year_and_month: the year and month to be displayed in the format ``YYYY/MM`` (the month may be single-digit) :type request: HttpRequest :type process_name: str :type year_and_month: str :return: the HTTP response object :rtype: HttpResponse """ process_class = get_all_models()[process_name] process_name = camel_case_to_underscores(process_name) permissions.assert_can_view_lab_notebook(request.user, process_class) if not year_and_month: try: timestamp = process_class.objects.latest().timestamp except process_class.DoesNotExist: timestamp = datetime.datetime.today() return HttpResponseSeeOther("{0}/{1}".format(timestamp.year, timestamp.month)) year, month = parse_year_and_month(year_and_month) if request.method == "POST": year_month_form = YearMonthForm(request.POST) if year_month_form.is_valid(): return HttpResponseSeeOther(django.core.urlresolvers.reverse( "lab_notebook_" + process_name, kwargs={"year_and_month": "{year}/{month}".format(**year_month_form.cleaned_data)})) else: year_month_form = YearMonthForm(initial={"year": year, "month": month}) template = loader.get_template("samples/lab_notebook_" + process_name + ".html") template_context = RequestContext(request, process_class.get_lab_notebook_context(year, month)) html_body = template.render(template_context) previous_url, next_url = get_previous_next_urls(process_name, year, month) try: export_url = django.core.urlresolvers.reverse( "export_lab_notebook_" + process_name, kwargs={"year_and_month": year_and_month}) + "?next=" + urlquote_plus(request.path) except django.core.urlresolvers.NoReverseMatch: export_url = None return render(request, "samples/lab_notebook.html", {"title": capitalize_first_letter(_("lab notebook for {process_name}") .format(process_name=process_class._meta.verbose_name_plural)), "year": year, "month": month, "year_month": year_month_form, "html_body": html_body, "previous_url": previous_url, "next_url": next_url, "export_url": export_url})
def parse_data(self, data, prefix): """Create all forms associated with this node (all the seach fields, and the `SearchModelForm` for all children), and create recursively the tree by creating the children. :param data: the GET dictionary of the request; may be ``None`` if the forms of this node are supposed to be unbound (because it was newly created and there's nothing to be parsed into them) :param prefix: The prefix for the forms. Note that the form to select a model does not belong to the model to be selected but to its parent model. This is also true for the prefixes: The top-level selection form (called ``root_form`` in ``samples.view.sample.advanced_search``) doesn't have a prefix, neither have the top-level search fields. The children of a node have the next nesting depth of the prefix, including the `SearchModelForm` in which they were selected. The starting number of prefixes is 1, and the nesting levels are separated by dashs. :type data: QueryDict or NoneType :type prefix: str """ for search_field in self.search_fields: search_field.parse_data(data, prefix) data = data or {} depth = prefix.count("-") + (2 if prefix else 1) keys = [key for key in data if key.count("-") == depth] i = 1 while True: new_prefix = prefix + ("-" if prefix else "") + str(i) if not data.get(new_prefix + "-_model"): break search_model_form = SearchModelForm(self.related_models.keys(), data, prefix=new_prefix) if not search_model_form.is_valid(): break model_name = data[new_prefix + "-_model"] node = utils.get_all_models()[model_name].get_search_tree_node() parse_node = search_model_form.cleaned_data[ "_model"] == search_model_form.cleaned_data["_old_model"] node.parse_data(data if parse_node else None, new_prefix) search_model_form = SearchModelForm( self.related_models.keys(), initial={ "_old_model": search_model_form.cleaned_data["_model"], "_model": search_model_form.cleaned_data["_model"] }, prefix=new_prefix) self.children.append((search_model_form, node)) i += 1 if self.related_models: self.children.append((SearchModelForm(self.related_models.keys(), prefix=new_prefix), None))
def get_addable_models(user): """Return a list with all registered addable model classes the user is allowed to see (the classes per se; this is not about the visibility of the model instances). Their type is of `PermissionsModels`, which means that they contain information about the users who have permissions for that model. You can see all addable model classes of your department. A superuser can see all classes. The result is used to build the list of apparatuses for which one can set permissions. :param user: The user for which the classes are returned that he is allowed to see. :type user: django.contrib.auth.models.User :return: all addable models for the user :rtype: list of `django.db.models.Model` """ all_addable_models = [] for model in get_all_models().values(): if model._meta.app_label not in ["samples", "jb_common"]: permission_codename = "edit_permissions_for_{0}".format( model.__name__.lower()) content_type = ContentType.objects.get_for_model(model) try: Permission.objects.get(codename=permission_codename, content_type=content_type) except Permission.DoesNotExist: continue else: all_addable_models.append(model) if not user.is_superuser: user_department = user.jb_user_details.department if user_department: all_addable_models = [ model for model in all_addable_models if model._meta.app_label == user_department.app_label ] else: all_addable_models = [] all_addable_models.sort( key=lambda model: model._meta.verbose_name_plural.lower()) all_addable_models = [ PermissionsModels(model) for model in all_addable_models ] return all_addable_models
def show_process(request, process_id, process_name="Process"): """Show an existing physical process. This is some sort of fallback view in case a process doesn't provide its own show view (which is mostly the case). The ``process_id`` needn't be the ``"id"`` field: If `process_name` is not ``None``, its ``JBMeta.identifying_field``, it given, is used instead for the lookup. :param request: the current HTTP Request object :param process_id: the ID or the process's identifying field value :param process_name: the class name of the process; if ``None``, ``Process`` is assumed :type request: HttpRequest :type process_id: unicode :type process_name: unicode :return: the HTTP response object :rtype: HttpResponse """ process_class = get_all_models()[process_name] try: identifying_field = process_class.JBMeta.identifying_field except AttributeError: identifying_field = "id" try: process = get_object_or_404(process_class, **{ identifying_field: process_id }).actual_instance except ValueError: raise Http404("Invalid value for {} passed: {}".format( identifying_field, repr(process_id))) if not isinstance(process, models.PhysicalProcess): raise Http404("No physical process with that ID was found.") permissions.assert_can_view_physical_process(request.user, process) if is_json_requested(request): return respond_in_json(process.get_data()) template_context = { "title": six.text_type(process), "samples": process.samples.all(), "process": process } template_context.update(utils.digest_process(process, request.user)) return render(request, "samples/show_process.html", template_context)
def parse_data(self, data, prefix): """Create all forms associated with this node (all the seach fields, and the `SearchModelForm` for all children), and create recursively the tree by creating the children. :param data: the GET dictionary of the request; may be ``None`` if the forms of this node are supposed to be unbound (because it was newly created and there's nothing to be parsed into them) :param prefix: The prefix for the forms. Note that the form to select a model does not belong to the model to be selected but to its parent model. This is also true for the prefixes: The top-level selection form (called ``root_form`` in ``samples.view.sample.advanced_search``) doesn't have a prefix, neither have the top-level search fields. The children of a node have the next nesting depth of the prefix, including the `SearchModelForm` in which they were selected. The starting number of prefixes is 1, and the nesting levels are separated by dashs. :type data: QueryDict or NoneType :type prefix: str """ for search_field in self.search_fields: search_field.parse_data(data, prefix) data = data or {} depth = prefix.count("-") + (2 if prefix else 1) keys = [key for key in data if key.count("-") == depth] i = 1 while True: new_prefix = prefix + ("-" if prefix else "") + str(i) if not data.get(new_prefix + "-_model"): break search_model_form = SearchModelForm(self.related_models.keys(), data, prefix=new_prefix) if not search_model_form.is_valid(): break model_name = data[new_prefix + "-_model"] node = utils.get_all_models()[model_name].get_search_tree_node() parse_node = search_model_form.cleaned_data["_model"] == search_model_form.cleaned_data["_old_model"] node.parse_data(data if parse_node else None, new_prefix) search_model_form = SearchModelForm(self.related_models.keys(), initial={"_old_model": search_model_form.cleaned_data["_model"], "_model": search_model_form.cleaned_data["_model"]}, prefix=new_prefix) self.children.append((search_model_form, node)) i += 1 if self.related_models: self.children.append((SearchModelForm(self.related_models.keys(), prefix=new_prefix), None))
def get_addable_models(user): """Return a list with all registered addable model classes the user is allowed to see (the classes per se; this is not about the visibility of the model instances). Their type is of `PermissionsModels`, which means that they contain information about the users who have permissions for that model. You can see all addable model classes of your department. A superuser can see all classes. The result is used to build the list of apparatuses for which one can set permissions. :param user: The user for which the classes are returned that he is allowed to see. :type user: django.contrib.auth.models.User :return: all addable models for the user :rtype: list of `django.db.models.Model` """ all_addable_models = [] for model in get_all_models().values(): if model._meta.app_label not in ["samples", "jb_common"]: permission_codename = "edit_permissions_for_{0}".format(model.__name__.lower()) content_type = ContentType.objects.get_for_model(model) try: Permission.objects.get(codename=permission_codename, content_type=content_type) except Permission.DoesNotExist: continue else: all_addable_models.append(model) if not user.is_superuser: user_department = user.jb_user_details.department if user_department: all_addable_models = [model for model in all_addable_models if model._meta.app_label == user_department.app_label] else: all_addable_models = [] all_addable_models.sort(key=lambda model: model._meta.verbose_name_plural.lower()) all_addable_models = [PermissionsModels(model) for model in all_addable_models] return all_addable_models
def export(request, process_name, year_and_month): """View for exporting the data of a month of a lab notebook. Thus, the return value is not an HTML response but a CSV or JSON response. In ``urls.py``, you must give the entry for this view the name ``"export_lab_notebook_<process_name>"``. :param request: the current HTTP Request object :param process_name: the class name of the model of the physical process, e.g. ``"LargeAreaDeposition"`` :param year_and_month: the year and month to be displayed in the format ``YYYY/MM`` (the month may be single-digit) :type request: HttpRequest :type process_name: str :type year_and_month: str :return: the HTTP response object :rtype: HttpResponse """ process_class = get_all_models()[process_name] permissions.assert_can_view_lab_notebook(request.user, process_class) year, month = parse_year_and_month(year_and_month) data = process_class.get_lab_notebook_data(year, month) result = utils.table_export(request, data, _("process")) if isinstance(result, tuple): column_groups_form, columns_form, table, switch_row_forms, old_data_form = result elif isinstance(result, HttpResponse): return result title = _("Table export for “{name}”").format(name=data.descriptive_name) return render( request, "samples/table_export.html", { "title": title, "column_groups": column_groups_form, "columns": columns_form, "rows": list(zip(table, switch_row_forms)) if table else None, "old_data": old_data_form, "backlink": request.GET.get("next", "") })
def export(request, process_name, year_and_month): """View for exporting the data of a month of a lab notebook. Thus, the return value is not an HTML response but a CSV or JSON response. In ``urls.py``, you must give the entry for this view the name ``"export_lab_notebook_<process_name>"``. :param request: the current HTTP Request object :param process_name: the class name of the model of the physical process, e.g. ``"LargeAreaDeposition"`` :param year_and_month: the year and month to be displayed in the format ``YYYY/MM`` (the month may be single-digit) :type request: HttpRequest :type process_name: str :type year_and_month: str :return: the HTTP response object :rtype: HttpResponse """ process_class = get_all_models()[process_name] permissions.assert_can_view_lab_notebook(request.user, process_class) year, month = parse_year_and_month(year_and_month) data = process_class.get_lab_notebook_data(year, month) result = utils.table_export(request, data, _("process")) if isinstance(result, tuple): column_groups_form, columns_form, table, switch_row_forms, old_data_form = result elif isinstance(result, HttpResponse): return result title = _("Table export for “{name}”").format(name=data.descriptive_name) return render(request, "samples/table_export.html", {"title": title, "column_groups": column_groups_form, "columns": columns_form, "rows": list(zip(table, switch_row_forms)) if table else None, "old_data": old_data_form, "backlink": request.GET.get("next", "")})
def __init__(self, user, *args, **kwargs): super(UserDetailsForm, self).__init__(*args, **kwargs) self.fields["auto_addition_topics"].queryset = user.topics choices = [] processes = [process_class for process_class in jb_common_utils.get_all_models().values() if issubclass(process_class, models.Process) and not process_class._meta.abstract and process_class not in [models.Process, models.Deposition]] for department in user.samples_user_details.show_users_from_departments.iterator(): process_from_department = set(process for process in processes if process._meta.app_label == department.app_label) choices.append((department.name, utils.choices_of_content_types(process_from_department))) if not choices: choices = (("", 9 * "-"),) self.fields["default_folded_process_classes"].choices = choices self.fields["default_folded_process_classes"].initial = [content_type.id for content_type in user.samples_user_details.default_folded_process_classes.iterator()] self.fields["default_folded_process_classes"].widget.attrs["size"] = "15" self.fields["subscribed_feeds"].choices = utils.choices_of_content_types( list(get_all_addable_physical_process_models()) + [models.Sample, models.SampleSeries, Topic]) self.fields["subscribed_feeds"].widget.attrs["size"] = "15" self.fields["show_users_from_departments"].choices = [(department.pk, department.name) for department in Department.objects.iterator()] self.fields["show_users_from_departments"].initial = \ user.samples_user_details.show_users_from_departments.values_list("id", flat=True)
def __init__(self, user, *args, **kwargs): super(UserDetailsForm, self).__init__(*args, **kwargs) self.fields["auto_addition_topics"].queryset = user.topics choices = [] processes = [process_class for process_class in jb_common_utils.get_all_models().values() if issubclass(process_class, models.Process) and not process_class._meta.abstract and process_class not in [models.Process, models.Deposition]] for department in user.samples_user_details.show_users_from_departments.iterator(): processes_from_department = set(process for process in processes if process._meta.app_label == department.app_label) choices.append((department.name, utils.choices_of_content_types(processes_from_department))) if not choices: choices = (("", 9 * "-"),) self.fields["default_folded_process_classes"].choices = choices self.fields["default_folded_process_classes"].initial = [content_type.id for content_type in user.samples_user_details.default_folded_process_classes.iterator()] self.fields["default_folded_process_classes"].widget.attrs["size"] = "15" self.fields["subscribed_feeds"].choices = utils.choices_of_content_types( list(get_all_addable_physical_process_models()) + [models.Sample, models.SampleSeries, Topic]) self.fields["subscribed_feeds"].widget.attrs["size"] = "15" self.fields["show_users_from_departments"].choices = [(department.pk, department.name) for department in Department.objects.iterator()] self.fields["show_users_from_departments"].initial = \ user.samples_user_details.show_users_from_departments.values_list("id", flat=True)
def show(request, process_name, year_and_month): """View for showing one month of the lab notebook for a particular physical process. In ``urls.py``, you must give the entry for this view the name ``"lab_notebook_<camel_case_process_name>"``. :param request: the current HTTP Request object :param process_name: the class name of the model of the physical process, e.g. ``"LargeAreaDeposition"`` :param year_and_month: the year and month to be displayed in the format ``YYYY/MM`` (the month may be single-digit) :type request: HttpRequest :type process_name: str :type year_and_month: str :return: the HTTP response object :rtype: HttpResponse """ process_class = get_all_models()[process_name] process_name = camel_case_to_underscores(process_name) namespace = process_class._meta.app_label permissions.assert_can_view_lab_notebook(request.user, process_class) if not year_and_month: try: timestamp = process_class.objects.latest().timestamp except process_class.DoesNotExist: timestamp = datetime.datetime.today() return HttpResponseSeeOther("{0}/{1}".format(timestamp.year, timestamp.month)) year, month = parse_year_and_month(year_and_month) if request.method == "POST": year_month_form = YearMonthForm(request.POST) if year_month_form.is_valid(): return HttpResponseSeeOther( django.core.urlresolvers.reverse( "{}:lab_notebook_{}".format(namespace, process_name), kwargs={ "year_and_month": "{year}/{month}".format(**year_month_form.cleaned_data) })) else: year_month_form = YearMonthForm(initial={"year": year, "month": month}) template = loader.get_template("samples/lab_notebook_" + process_name + ".html") template_context = RequestContext( request, process_class.get_lab_notebook_context(year, month)) html_body = template.render(template_context.flatten()) previous_url, next_url = get_previous_next_urls(process_name, namespace, year, month) try: export_url = django.core.urlresolvers.reverse( "{}:export_lab_notebook_{}".format(namespace, process_name), kwargs={"year_and_month": year_and_month }) + "?next=" + urlquote_plus(request.path) except django.core.urlresolvers.NoReverseMatch: export_url = None return render( request, "samples/lab_notebook.html", { "title": capitalize_first_letter( _("lab notebook for {process_name}").format( process_name=process_class._meta.verbose_name_plural)), "year": year, "month": month, "year_month": year_month_form, "html_body": html_body, "previous_url": previous_url, "next_url": next_url, "export_url": export_url })
def advanced_search(request): """View for searching for samples, sample series, physical processes, and results. The visibility rules of the search results are the same as for the sample search. Additionally, you can only see sample series you are the currently responsible person of or that are in one of your topics. A POST request on this URL will add samples to the “My Samples” list. *All* search parameters are in the query string, so if you just want to search, this is a GET requets. Therefore, this view has two submit buttons. :param request: the current HTTP Request object :type request: HttpRequest :return: the HTTP response object :rtype: HttpResponse """ model_list = [model for model in jb_common.search.get_all_searchable_models() if hasattr(model, "get_absolute_url")] search_tree = None results, add_forms = [], [] too_many_results = False root_form = jb_common.search.SearchModelForm(model_list, request.GET) search_performed = False no_permission_message = None _search_parameters_hash = hashlib.sha1(json.dumps(sorted(dict((key, value) for key, value in request.GET.items() if not "__" in key and key != "_search_parameters_hash").items())).encode("utf-8")).hexdigest() column_groups_form = columns_form = table = switch_row_forms = old_data_form = None if root_form.is_valid() and root_form.cleaned_data["_model"]: search_tree = get_all_models()[root_form.cleaned_data["_model"]].get_search_tree_node() parse_tree = root_form.cleaned_data["_model"] == root_form.cleaned_data["_old_model"] search_tree.parse_data(request.GET if parse_tree else None, "") if search_tree.is_valid(): if search_tree.model_class == models.Sample: base_query = utils.restricted_samples_query(request.user) elif search_tree.model_class == models.SampleSeries: base_query = models.SampleSeries.objects.filter( Q(topic__confidential=False) | Q(topic__members=request.user) | Q(currently_responsible_person=request.user)).distinct() else: base_query = None results, too_many_results = jb_common.search.get_search_results(search_tree, max_results, base_query) if search_tree.model_class == models.Sample: if request.method == "POST": sample_ids = set(int_or_zero(key[2:].partition("-")[0]) for key, value in request.POST.items() if value == "on") samples = base_query.in_bulk(sample_ids).values() request.user.my_samples.add(*samples) my_samples = request.user.my_samples.all() add_forms = [AddToMySamplesForm(prefix="0-" + str(sample.pk)) if sample not in my_samples else None for sample in results] else: add_forms = len(results) * [None] if results and root_form.cleaned_data["_search_parameters_hash"] == _search_parameters_hash: data_node = data_tree.DataNode(_("search results")) for result in results: insert = False if isinstance(result, models.PhysicalProcess) \ and permissions.has_permission_to_view_physical_process(request.user, result): insert = True elif isinstance(result, models.Result) \ and permissions.has_permission_to_view_result_process(request.user, result): insert = True elif isinstance(result, models.Sample) \ and permissions.has_permission_to_fully_view_sample(request.user, result): insert = True elif isinstance(result, models.SampleSeries) \ and permissions.has_permission_to_view_sample_series(request.user, result): insert = True if insert: data_node.children.append(result.get_data_for_table_export()) if len(data_node.children) == 0: no_permission_message = _("You don't have the permission to see any content of the search results.") else: export_result = utils.table_export(request, data_node, "") if isinstance(export_result, tuple): column_groups_form, columns_form, table, switch_row_forms, old_data_form = export_result elif isinstance(export_result, HttpResponse): return export_result search_performed = True root_form = jb_common.search.SearchModelForm( model_list, initial={"_old_model": root_form.cleaned_data["_model"], "_model": root_form.cleaned_data["_model"], "_search_parameters_hash": _search_parameters_hash}) else: root_form = jb_common.search.SearchModelForm(model_list) root_form.fields["_model"].label = "" content_dict = {"title": capfirst(_("advanced search")), "search_root": root_form, "search_tree": search_tree, "results": list(zip(results, add_forms)), "search_performed": search_performed, "something_to_add": any(add_forms), "too_many_results": too_many_results, "max_results": max_results, "column_groups": column_groups_form, "columns": columns_form, "old_data": old_data_form, "rows": list(zip(table, switch_row_forms)) if table else None, "no_permission_message": no_permission_message} return render(request, "samples/advanced_search.html", content_dict)