|
50 | 50 | MembershipCSVImportResource, |
51 | 51 | ) |
52 | 52 | from core.organization_claim import make_organization_claim_token |
| 53 | +from core.organization_csv_import import ( |
| 54 | + _ORG_COLUMN_FIELDS, |
| 55 | + OrganizationCSVConfirmImportForm, |
| 56 | + OrganizationCSVImportForm, |
| 57 | + OrganizationCSVImportResource, |
| 58 | + iter_representative_selection_items, |
| 59 | + optional_organization_csv_columns, |
| 60 | + required_organization_csv_columns, |
| 61 | +) |
53 | 62 | from core.protected_resources import protected_freeipa_group_cns |
54 | 63 | from core.user_labels import user_choice, user_choice_with_fallback, user_choices_from_users |
55 | 64 | from core.views_utils import _normalize_str |
|
81 | 90 | MembershipType, |
82 | 91 | MembershipTypeCategory, |
83 | 92 | Organization, |
| 93 | + OrganizationCSVImportLink, |
84 | 94 | VotingCredential, |
85 | 95 | ) |
86 | 96 |
|
@@ -1831,6 +1841,137 @@ def changelist_view(self, request: HttpRequest, extra_context: dict[str, Any] | |
1831 | 1841 | return redirect("admin:core_membershipcsvimportlink_import") |
1832 | 1842 |
|
1833 | 1843 |
|
| 1844 | +@admin.register(OrganizationCSVImportLink) |
| 1845 | +class OrganizationCSVImportLinkAdmin(ImportMixin, admin.ModelAdmin): |
| 1846 | + """Admin entry for the organization CSV importer (django-import-export).""" |
| 1847 | + |
| 1848 | + import_form_class = OrganizationCSVImportForm |
| 1849 | + confirm_form_class = OrganizationCSVConfirmImportForm |
| 1850 | + import_template_name = "admin/core/organization_csv_import.html" |
| 1851 | + resource_classes = [OrganizationCSVImportResource] |
| 1852 | + |
| 1853 | + @override |
| 1854 | + def has_add_permission(self, request: HttpRequest) -> bool: |
| 1855 | + return False |
| 1856 | + |
| 1857 | + @override |
| 1858 | + def get_import_formats(self) -> list[type[base_formats.Format]]: |
| 1859 | + return [base_formats.CSV] |
| 1860 | + |
| 1861 | + @override |
| 1862 | + def has_delete_permission(self, request: HttpRequest, obj: object | None = None) -> bool: |
| 1863 | + return False |
| 1864 | + |
| 1865 | + @override |
| 1866 | + def has_change_permission(self, request: HttpRequest, obj: object | None = None) -> bool: |
| 1867 | + return False |
| 1868 | + |
| 1869 | + @override |
| 1870 | + def has_view_permission(self, request: HttpRequest, obj: object | None = None) -> bool: |
| 1871 | + return bool(request.user.is_active and request.user.is_staff) |
| 1872 | + |
| 1873 | + @override |
| 1874 | + def get_model_perms(self, request: HttpRequest) -> dict[str, bool]: |
| 1875 | + if not self.has_view_permission(request): |
| 1876 | + return {} |
| 1877 | + return {"view": True} |
| 1878 | + |
| 1879 | + @override |
| 1880 | + def get_confirm_form_initial(self, request: HttpRequest, import_form: forms.Form) -> dict[str, Any]: |
| 1881 | + initial = super().get_confirm_form_initial(request, import_form) |
| 1882 | + if import_form is None: |
| 1883 | + return initial |
| 1884 | + |
| 1885 | + for key in _ORG_COLUMN_FIELDS: |
| 1886 | + value = import_form.cleaned_data.get(key, "") |
| 1887 | + if value: |
| 1888 | + initial[key] = value |
| 1889 | + return initial |
| 1890 | + |
| 1891 | + @override |
| 1892 | + def get_import_resource_kwargs(self, request: HttpRequest, **kwargs: Any) -> dict[str, Any]: |
| 1893 | + form = kwargs.get("form") |
| 1894 | + if form is None: |
| 1895 | + raise ValueError("Missing import form") |
| 1896 | + |
| 1897 | + cleaned_data = getattr(form, "cleaned_data", None) |
| 1898 | + extra: dict[str, Any] = {} |
| 1899 | + if isinstance(cleaned_data, dict): |
| 1900 | + for key in _ORG_COLUMN_FIELDS: |
| 1901 | + value = cleaned_data.get(key, "") |
| 1902 | + if value: |
| 1903 | + extra[key] = value |
| 1904 | + |
| 1905 | + representative_selections = iter_representative_selection_items(request.POST.items()) |
| 1906 | + |
| 1907 | + return { |
| 1908 | + "actor_username": request.user.get_username(), |
| 1909 | + "representative_selections": representative_selections, |
| 1910 | + **extra, |
| 1911 | + } |
| 1912 | + |
| 1913 | + @override |
| 1914 | + def import_action(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: |
| 1915 | + response = super().import_action(request, *args, **kwargs) |
| 1916 | + |
| 1917 | + if not isinstance(response, TemplateResponse) or response.context_data is None: |
| 1918 | + return response |
| 1919 | + |
| 1920 | + response.context_data["required_csv_columns"] = required_organization_csv_columns() |
| 1921 | + response.context_data["optional_csv_columns"] = optional_organization_csv_columns() |
| 1922 | + |
| 1923 | + result = response.context_data.get("result") |
| 1924 | + confirm_form = response.context_data.get("confirm_form") |
| 1925 | + if result is None or confirm_form is None: |
| 1926 | + return response |
| 1927 | + |
| 1928 | + valid_rows_obj = getattr(result, "valid_rows", None) |
| 1929 | + if callable(valid_rows_obj): |
| 1930 | + valid_rows = list(valid_rows_obj() or []) |
| 1931 | + else: |
| 1932 | + valid_rows = list(valid_rows_obj or []) |
| 1933 | + |
| 1934 | + matches: list[Any] = [] |
| 1935 | + skipped: list[Any] = [] |
| 1936 | + |
| 1937 | + for idx, row_result in enumerate(valid_rows, start=1): |
| 1938 | + instance = row_result.instance if hasattr(row_result, "instance") else None |
| 1939 | + import_type = row_result.import_type if hasattr(row_result, "import_type") else "" |
| 1940 | + if import_type: |
| 1941 | + is_match = import_type != "skip" |
| 1942 | + elif instance is not None and hasattr(instance, "decision"): |
| 1943 | + is_match = instance.decision == "IMPORT" |
| 1944 | + else: |
| 1945 | + is_match = False |
| 1946 | + |
| 1947 | + number = getattr(row_result, "number", None) |
| 1948 | + if number is None: |
| 1949 | + number = idx |
| 1950 | + try: |
| 1951 | + row_result.astra_row_number = int(number) |
| 1952 | + except (TypeError, ValueError): |
| 1953 | + row_result.astra_row_number = idx |
| 1954 | + |
| 1955 | + if is_match: |
| 1956 | + matches.append(row_result) |
| 1957 | + else: |
| 1958 | + skipped.append(row_result) |
| 1959 | + |
| 1960 | + response.context_data["preview_summary"] = { |
| 1961 | + "total": len(valid_rows), |
| 1962 | + "to_import": len(matches), |
| 1963 | + "skipped": len(skipped), |
| 1964 | + } |
| 1965 | + response.context_data["matches_page_obj"] = Paginator(matches, 50).get_page(request.GET.get("matches_page") or "1") |
| 1966 | + response.context_data["skipped_page_obj"] = Paginator(skipped, 50).get_page(request.GET.get("skipped_page") or "1") |
| 1967 | + |
| 1968 | + return response |
| 1969 | + |
| 1970 | + @override |
| 1971 | + def changelist_view(self, request: HttpRequest, extra_context: dict[str, Any] | None = None) -> HttpResponse: |
| 1972 | + return redirect("admin:core_organizationcsvimportlink_import") |
| 1973 | + |
| 1974 | + |
1834 | 1975 | @admin.register(Organization) |
1835 | 1976 | class OrganizationAdmin(admin.ModelAdmin): |
1836 | 1977 | class OrganizationAdminForm(forms.ModelForm): |
|
0 commit comments