diff --git a/district/forms.py b/district/forms.py
index 45b12efafb86376aa240cb2a98fcf530fa4e5405..af137ed79c83a6f933d7fcb069bea07a7c264f4b 100644
--- a/district/forms.py
+++ b/district/forms.py
@@ -1,13 +1,15 @@
 import os
 
 from shared.forms import JekyllImportForm as SharedJekyllImportForm
+from shared.forms import OctopusPeopleImportForm as SharedOctopusPeopleImportForm
 
 from .tasks import import_jekyll_articles
+from .tasks import import_people_from_group
 
 
 class JekyllImportForm(SharedJekyllImportForm):
     def handle_import(self):
-        lock_file_name = f"/tmp/.{self.instance.id}.import-lock"
+        lock_file_name = f"/tmp/.{self.instance.id}.articles-import-lock"
 
         if os.path.isfile(lock_file_name):
             return
@@ -21,3 +23,21 @@ class JekyllImportForm(SharedJekyllImportForm):
             dry_run=self.cleaned_data["dry_run"],
             use_git=True,
         )
+
+
+class OctopusImportForm(SharedOctopusPeopleImportForm):
+    def handle_import_from_group(self):
+        lock_file_name = f"/tmp/.{self.instance.id}.people-from-group-import-lock"
+
+        if os.path.isfile(lock_file_name):
+            return
+
+        open(lock_file_name, "w").close()
+
+        import_people_from_group.delay(
+            people_parent_page_id=self.instance.id,
+            collection_id=self.cleaned_data["collection"].id,
+            group_shortcut=self.cleaned_data["group_shortcut"],
+            dry_run=self.cleaned_data["dry_run"],
+            lock_file_name=lock_file_name
+        )
\ No newline at end of file
diff --git a/district/models.py b/district/models.py
index 4ee9414e8280393102c5ddd5477f05fb13cb53d1..10fad543451eb066838e281e0540655e491e1584 100644
--- a/district/models.py
+++ b/district/models.py
@@ -69,7 +69,7 @@ from shared.utils import (
 )
 
 from . import blocks
-from .forms import JekyllImportForm
+from .forms import JekyllImportForm, OctopusImportForm
 
 CONTENT_BLOCKS = DEFAULT_CONTENT_BLOCKS + [
     ("chart", ChartBlock()),
@@ -390,6 +390,8 @@ class DistrictPersonPage(MainPersonPageMixin):
 
 
 class DistrictPeoplePage(MainPeoplePageMixin):
+    base_form_class = OctopusImportForm
+
     content = StreamField(
         [
             ("people_group", blocks.PeopleGroupBlock(label="Seznam osob")),
@@ -403,6 +405,26 @@ class DistrictPeoplePage(MainPeoplePageMixin):
     parent_page_types = ["district.DistrictHomePage"]
     subpage_types = ["district.DistrictPersonPage"]
 
+    import_panels = [
+        MultiFieldPanel(
+            [
+                FieldPanel("do_import"),
+                FieldPanel("collection"),
+                FieldPanel("dry_run"),
+                FieldPanel("group_shortcut")
+            ],
+            "import osob z Chobotnice",
+        ),
+    ]
+
+    edit_handler = TabbedInterface(
+        [
+            ObjectList(MainPeoplePageMixin.content_panels, heading="Obsah"),
+            ObjectList(MainPeoplePageMixin.promote_panels, heading="Metadata"),
+            ObjectList(import_panels, heading="Import"),
+        ]
+    )
+
 
 class DistrictCalendarPage(SubpageMixin, MetadataPageMixin, CalendarMixin, Page):
     ### PANELS
diff --git a/district/tasks.py b/district/tasks.py
index 4870b7020008da8079a433f2307b1d35cbae169a..0e6413255b53df6ff50b7199db93dd98977ae374 100644
--- a/district/tasks.py
+++ b/district/tasks.py
@@ -3,6 +3,7 @@ import logging
 from celery import shared_task
 
 from shared.jekyll_import import JekyllArticleImporter
+from shared.people_import import PeopleGroupImporter
 
 logger = logging.getLogger(__name__)
 
@@ -26,3 +27,24 @@ def import_jekyll_articles(
         use_git=use_git,
         page_model=DistrictArticlePage,
     ).perform_import()
+
+
+@shared_task()
+def import_people_from_group(
+    people_parent_page_id,
+    collection_id,
+    group_shortcut,
+    dry_run,
+    lock_file_name
+):
+    from .models import DistrictPeoplePage, DistrictPersonPage
+
+    return PeopleGroupImporter(
+        people_parent_page_id=people_parent_page_id,
+        people_parent_page_model=DistrictPeoplePage,
+        person_page_model=DistrictPersonPage,
+        collection_id=collection_id,
+        group_shortcut=group_shortcut,
+        dry_run=dry_run,
+        lock_file_name=lock_file_name,
+    ).perform_import()
\ No newline at end of file
diff --git a/majak/settings/base.py b/majak/settings/base.py
index a01e7398a7b448dcdd7a76e119b6a6e40e899781..0c69ff0c4fb16ba6ea7bce04b13e78818e9a5626 100644
--- a/majak/settings/base.py
+++ b/majak/settings/base.py
@@ -269,6 +269,7 @@ WAGTAILADMIN_BASE_URL = BASE_URL
 # CUSTOM SETTINGS
 # ------------------------------------------------------------------------------
 STYLEGUIDE_URL = env.str("STYLEGUIDE_URL", "https://styleguide.pirati.cz/2.20.x/")
+OCTOPUS_API_URL = env.str("OCTOPUS_API_URL", "https://chobotnice.pirati.cz/graphql/")
 
 MAJAK_ENV = env.str("MAJAK_ENV", default="prod")
 
diff --git a/requirements/base.in b/requirements/base.in
index a9fe3d4b8abc669786c1769ce80d327157570295..ec7fe4126a499ac2f918f7f8f2d01db842e70ccd 100644
--- a/requirements/base.in
+++ b/requirements/base.in
@@ -8,6 +8,7 @@ django-redis
 django-settings-export
 django-widget-tweaks
 django-simple-captcha
+gql[all]
 psycopg2-binary
 pirates
 whitenoise==5.3.0
diff --git a/requirements/base.txt b/requirements/base.txt
index 99e6ccd5b262e1b57aa9df9215e72a9268c74dc3..05903242b15815365d44fee95ff2cd379f003c75 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -4,10 +4,18 @@
 #
 #    pip-compile base.in
 #
+aiohttp==3.9.5
+    # via gql
+aiosignal==1.3.1
+    # via aiohttp
 amqp==5.2.0
     # via kombu
 anyascii==0.3.2
     # via wagtail
+anyio==4.4.0
+    # via
+    #   gql
+    #   httpx
 arrow==1.3.0
     # via
     #   -r base.in
@@ -18,9 +26,12 @@ asttokens==2.4.1
     # via stack-data
 attrs==23.2.0
     # via
+    #   aiohttp
     #   cattrs
     #   ics
     #   requests-cache
+backoff==2.2.1
+    # via gql
 beautifulsoup4==4.12.3
     # via
     #   -r base.in
@@ -29,14 +40,18 @@ billiard==4.2.0
     # via celery
 bleach==6.1.0
     # via -r base.in
+botocore==1.34.149
+    # via gql
 brotli==1.1.0
     # via fonttools
 cattrs==23.2.3
     # via requests-cache
 celery==5.4.0
     # via -r base.in
-certifi==2024.6.2
+certifi==2024.7.4
     # via
+    #   httpcore
+    #   httpx
     #   requests
     #   sentry-sdk
 cffi==1.16.0
@@ -57,7 +72,7 @@ click-plugins==1.1.1
     # via celery
 click-repl==0.3.0
     # via celery
-cryptography==42.0.8
+cryptography==43.0.0
     # via
     #   josepy
     #   mozilla-django-oidc
@@ -68,7 +83,7 @@ decorator==5.1.1
     # via ipython
 defusedxml==0.7.1
     # via willow
-django==5.0.6
+django==5.0.7
     # via
     #   -r base.in
     #   django-extensions
@@ -109,7 +124,7 @@ django-treebeard==4.7.1
     # via wagtail
 django-widget-tweaks==1.5.0
     # via -r base.in
-djangorestframework==3.15.1
+djangorestframework==3.15.2
     # via wagtail
 draftjs-exporter==5.0.0
     # via wagtail
@@ -117,26 +132,46 @@ et-xmlfile==1.1.0
     # via openpyxl
 executing==2.0.1
     # via stack-data
-fastjsonschema==2.19.1
+fastjsonschema==2.20.0
     # via -r base.in
 filetype==1.2.0
     # via willow
-fonttools[woff]==4.53.0
+fonttools[woff]==4.53.1
     # via weasyprint
+frozenlist==1.4.1
+    # via
+    #   aiohttp
+    #   aiosignal
+gql[all]==3.5.0
+    # via -r base.in
+graphql-core==3.2.3
+    # via gql
+h11==0.14.0
+    # via httpcore
 html5lib==1.1
     # via weasyprint
+httpcore==1.0.5
+    # via httpx
 httplib2==0.22.0
     # via -r base.in
-icalendar==5.0.12
+httpx==0.27.0
+    # via gql
+icalendar==5.0.13
     # via -r base.in
 ics==0.7.2
     # via -r base.in
 idna==3.7
-    # via requests
-ipython==8.25.0
+    # via
+    #   anyio
+    #   httpx
+    #   requests
+    #   yarl
+ipython==8.26.0
     # via -r base.in
 jedi==0.19.1
     # via ipython
+jmespath==1.0.1
+    # via botocore
 josepy==1.14.0
     # via mozilla-django-oidc
 kombu==5.3.7
@@ -151,35 +186,39 @@ matplotlib-inline==0.1.7
     # via ipython
 mozilla-django-oidc==3.0.0
     # via pirates
-nh3==0.2.17
+multidict==6.0.5
+    # via
+    #   aiohttp
+    #   yarl
+nh3==0.2.18
     # via -r base.in
-numpy==1.26.4
+numpy==2.0.1
     # via opencv-python
 oauthlib==3.2.2
     # via
     #   requests-oauthlib
     #   tweepy
-opencv-python==4.10.0.82
+opencv-python==4.10.0.84
     # via -r base.in
-openpyxl==3.1.3
+openpyxl==3.1.5
     # via wagtail
 parso==0.8.4
     # via jedi
 pexpect==4.9.0
     # via ipython
-pillow==10.3.0
+pillow==10.4.0
     # via
     #   django-simple-captcha
     #   pillow-heif
     #   wagtail
     #   weasyprint
-pillow-heif==0.16.0
+pillow-heif==0.18.0
     # via willow
 pirates==0.7.0
     # via -r base.in
 platformdirs==4.2.2
     # via requests-cache
-prompt-toolkit==3.0.46
+prompt-toolkit==3.0.47
     # via
     #   click-repl
     #   ipython
@@ -187,15 +226,15 @@ psycopg2-binary==2.9.9
     # via -r base.in
 ptyprocess==0.7.0
     # via pexpect
-pure-eval==0.2.2
+pure-eval==0.2.3
     # via stack-data
 pycparser==2.22
     # via cffi
-pydyf==0.10.0
+pydyf==0.11.0
     # via weasyprint
 pygments==2.18.0
     # via ipython
-pyopenssl==24.1.0
+pyopenssl==24.2.1
     # via josepy
 pyparsing==3.1.2
     # via httplib2
@@ -206,6 +245,7 @@ pyphen==0.15.0
 python-dateutil==2.9.0.post0
     # via
     #   arrow
+    #   botocore
     #   celery
     #   icalendar
     #   ics
@@ -217,21 +257,25 @@ pytz==2024.1
     #   l18n
 pyyaml==6.0.1
     # via -r base.in
-redis==5.0.5
+redis==5.0.7
     # via django-redis
 requests==2.32.3
     # via
     #   -r base.in
+    #   gql
     #   mozilla-django-oidc
     #   requests-cache
     #   requests-oauthlib
+    #   requests-toolbelt
     #   tweepy
     #   wagtail
-requests-cache==1.2.0
+requests-cache==1.2.1
     # via -r base.in
 requests-oauthlib==1.3.1
     # via tweepy
-sentry-sdk==2.5.0
+requests-toolbelt==1.0.0
+    # via gql
+sentry-sdk==2.11.0
     # via -r base.in
 six==1.16.0
     # via
@@ -242,9 +286,13 @@ six==1.16.0
     #   l18n
     #   python-dateutil
     #   url-normalize
+sniffio==1.3.1
+    # via
+    #   anyio
+    #   httpx
 soupsieve==2.5
     # via beautifulsoup4
-sqlparse==0.5.0
+sqlparse==0.5.1
     # via django
 stack-data==0.6.3
     # via ipython
@@ -264,14 +312,15 @@ tweepy==4.14.0
     # via -r base.in
 types-python-dateutil==2.9.0.20240316
     # via arrow
-typing-extensions==4.12.1
+typing-extensions==4.12.2
     # via ipython
 tzdata==2024.1
     # via celery
 url-normalize==1.4.3
     # via requests-cache
-urllib3==2.2.1
+urllib3==2.2.2
     # via
+    #   botocore
     #   requests
     #   requests-cache
     #   sentry-sdk
@@ -280,7 +329,7 @@ vine==5.1.0
     #   amqp
     #   celery
     #   kombu
-wagtail==6.1.2
+wagtail==6.1.3
     # via
     #   -r base.in
     #   wagtail-metadata
@@ -296,7 +345,7 @@ wand==0.6.13
     # via -r base.in
 wcwidth==0.2.13
     # via prompt-toolkit
-weasyprint==62.2
+weasyprint==62.3
     # via -r base.in
 webencodings==0.5.1
     # via
@@ -304,11 +353,17 @@ webencodings==0.5.1
     #   cssselect2
     #   html5lib
     #   tinycss2
+websockets==11.0.3
+    # via gql
 whitenoise==5.3.0
     # via -r base.in
 willow[heif]==1.8.0
     # via
     #   wagtail
     #   willow
+yarl==1.9.4
+    # via
+    #   aiohttp
+    #   gql
 zopfli==0.2.3
     # via fonttools
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 5e31598753b52465d30f19eb4f0b1ea8b6413a59..be2a914dccdc42495b33637ebff2bdcd2db2605b 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -6,17 +6,17 @@
 #
 asgiref==3.8.1
     # via django
-coverage[toml]==7.5.3
+coverage[toml]==7.6.0
     # via pytest-cov
-django==5.0.6
+django==5.0.7
     # via
     #   -r dev.in
     #   django-debug-toolbar
-django-debug-toolbar==4.4.2
+django-debug-toolbar==4.4.6
     # via -r dev.in
 factory-boy==3.3.0
     # via pytest-factoryboy
-faker==25.6.0
+faker==26.0.0
     # via factory-boy
 fastdiff==0.3.0
     # via snapshottest
@@ -26,14 +26,14 @@ inflection==0.5.1
     # via pytest-factoryboy
 iniconfig==2.0.0
     # via pytest
-packaging==24.0
+packaging==24.1
     # via
     #   pytest
     #   pytest-factoryboy
     #   pytest-sugar
 pluggy==1.5.0
     # via pytest
-pytest==8.2.2
+pytest==8.3.2
     # via
     #   -r dev.in
     #   pytest-cov
@@ -64,7 +64,7 @@ six==1.16.0
     #   snapshottest
 snapshottest==0.6.0
     # via -r dev.in
-sqlparse==0.5.0
+sqlparse==0.5.1
     # via
     #   django
     #   django-debug-toolbar
@@ -72,7 +72,7 @@ termcolor==2.4.0
     # via
     #   pytest-sugar
     #   snapshottest
-typing-extensions==4.12.1
+typing-extensions==4.12.2
     # via pytest-factoryboy
 wasmer==1.1.0
     # via fastdiff
diff --git a/requirements/production.txt b/requirements/production.txt
index dd49e7bd479eee29364f50a22416e3665fbd3d6b..ff1dc31cb646d5c98932f06edfd7a74901f0aedc 100644
--- a/requirements/production.txt
+++ b/requirements/production.txt
@@ -6,5 +6,5 @@
 #
 gunicorn==22.0.0
     # via -r production.in
-packaging==24.0
+packaging==24.1
     # via gunicorn
diff --git a/shared/forms.py b/shared/forms.py
index b9e90c8fd88acbdb3705287259401c0a14781354..e937d0aa25371702337f7c68a478362e1b6c04ec 100644
--- a/shared/forms.py
+++ b/shared/forms.py
@@ -9,6 +9,49 @@ class SubscribeForm(forms.Form):
     return_page_id = forms.IntegerField()
 
 
+class OctopusPeopleImportForm(WagtailAdminPageForm):
+    do_import = forms.BooleanField(
+        initial=False, required=False, label="Provést import osob z Chobotnice"
+    )
+    collection = forms.ModelChoiceField(
+        queryset=Collection.objects.all(), required=False, label="Kolekce obrázků"
+    )
+    dry_run = forms.BooleanField(
+        initial=True,
+        required=False,
+        label="Jenom na zkoušku",
+    )
+    group_shortcut = forms.CharField(
+        label="Zkratka skupiny osob",
+        required=False,
+    )
+
+    def clean(self):
+        cleaned_data = super().clean()
+
+        if not cleaned_data.get("do_import"):
+            return cleaned_data
+
+        if cleaned_data.get("do_import") and not self.instance.id:
+            self.add_error(
+                "do_import", "Import proveďte prosím až po vytvoření stránky"
+            )
+
+        if not cleaned_data.get("collection"):
+            self.add_error("collection", "Pro import je toto pole povinné")
+
+        if not cleaned_data.get("group_shortcut"):
+            self.add_error("group_shortcut", "Pro import je toto pole povinné")
+
+        return cleaned_data
+
+    def save(self, commit=True):
+        if self.cleaned_data.get("do_import"):
+            self.handle_import_from_group()
+
+        return super().save(commit=commit)
+
+
 class JekyllImportForm(WagtailAdminPageForm):
     do_import = forms.BooleanField(
         initial=False, required=False, label="Provést import z Jekyllu"
diff --git a/shared/models/main.py b/shared/models/main.py
index 0f2cd59455ec6d0ba61cfdcaa198d56822ce873f..d96f092976aa69d9061f0db8d48e6d6becbedfb2 100644
--- a/shared/models/main.py
+++ b/shared/models/main.py
@@ -1841,8 +1841,6 @@ class MainPeoplePageMixin(
 
     promote_panels = make_promote_panels()
 
-    settings_panels = []
-
     ### RELATIONS
 
     # NOTE: Must be overridden
diff --git a/shared/people_import.py b/shared/people_import.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d72905e8e7e44cb713fcc6274da51cb550a4064
--- /dev/null
+++ b/shared/people_import.py
@@ -0,0 +1,137 @@
+from gql import gql, Client
+from gql.transport.aiohttp import AIOHTTPTransport
+from django.conf import settings
+import os
+
+
+class PeopleGroupImporter:
+    def __init__(
+        self,
+        people_parent_page_id,
+        people_parent_page_model,
+        person_page_model,
+        collection_id,
+        group_shortcut,
+        lock_file_name,
+        dry_run,
+    ):
+        self.people_parent_page_id = people_parent_page_id
+        self.people_parent_page_model = people_parent_page_model
+        self.person_page_model = person_page_model
+        self.collection_id = collection_id
+        self.group_shortcut = group_shortcut
+        self.lock_file_name = lock_file_name
+        self.dry_run = dry_run
+
+        self.transport = AIOHTTPTransport(url=settings.OCTOPUS_API_URL)
+        self.client = Client(transport=self.transport, fetch_schema_from_transport=True)
+
+    def get_people_ids_from_group(self):
+        query = gql(
+            f"""
+                query {{
+                  allGroups(
+                    filters:
+                      {{
+                        shortcut: {{exact: "{self.group_shortcut}" }}
+                      }}
+                  ) {{
+                    edges {{
+                      node {{
+                        memberships {{
+                          person {{
+                            id
+                          }}
+                        }}
+                      }}
+                    }}
+                  }}
+                }}
+            """
+        )
+
+        result = self.client.execute(query)
+
+        user_ids = []
+
+        for node in result["allGroups"]["edges"]:
+            for membership in node["node"]["memberships"]:
+                user_ids.append(membership["person"]["id"])
+
+        return user_ids
+
+    def get_person_profile_from_id(self, id: str, kind: str):
+        query = gql(
+            f"""
+                query {{
+                  allProfiles(
+                    filters: {{
+                      person: {{
+                        id: "{id}"
+                      }},
+                      kind: {kind}
+                    }}
+                  ) {{
+                    edges {{
+                      node {{
+                        email
+                        facebookUrl
+                        flickrUrl
+                        instagramUrl
+                        kind
+                        mastodonUrl
+                        phone
+                        photo
+                        textLong
+                        textShort
+                        tiktokUrl
+                        twitterUrl
+                        url
+                        webUrl
+                        youtubeUrl
+                        person {{
+                          degreeAfterName
+                          degreeBeforeName
+                          displayName
+                          profilePhoto
+                        }}
+                      }}
+                    }}
+                  }}
+                }}
+            """
+        )
+
+        result = self.client.execute(query)
+
+        # Just return the first result, there should never be more than one in this case.
+        for node in result["allProfiles"]["edges"]:
+            return node["node"]
+
+        # If there are no results, return None.
+        return None
+
+
+    def perform_import(self):
+        people_ids = self.get_people_ids_from_group()
+
+        people_profiles = {}
+
+        for person_id in people_ids:
+            prirotizied_profiles = []
+
+            prirotizied_profiles.append(self.get_person_profile_from_id(person_id, "POLITICAL"))
+            prirotizied_profiles.append(self.get_person_profile_from_id(person_id, "PIRATE"))
+
+            for profile in prirotizied_profiles:
+                if profile is None:
+                    continue
+
+                people_profiles[person_id] = profile
+
+            if person_id not in people_profiles:
+                people_profiles[person_id] = None
+        
+        print(people_profiles)
+
+        os.remove(self.lock_file_name)
\ No newline at end of file