diff --git a/nbgrader/apps/quickstartapp.py b/nbgrader/apps/quickstartapp.py
index 462e1cd7..62d57441 100644
--- a/nbgrader/apps/quickstartapp.py
+++ b/nbgrader/apps/quickstartapp.py
@@ -79,6 +79,14 @@ class QuickStartApp(NbGrader):
         classes.append(QuickStartApp)
         return classes
 
+    def _course_folder_content_exists(self, course_path):
+        course_folder_content = {
+            "source_dir": os.path.join(course_path, "source"),
+            "config_file": os.path.join(course_path, "nbgrader_config.py")
+        }
+        exists = os.path.isdir(course_folder_content['source_dir']) or os.path.isfile(course_folder_content['config_file'])
+        return exists
+
     def start(self):
         super(QuickStartApp, self).start()
 
@@ -89,19 +97,23 @@ class QuickStartApp(NbGrader):
         # make sure it doesn't exist
         course_id = self.extra_args[0]
         course_path = os.path.abspath(course_id)
+
         if os.path.exists(course_path):
             if self.force:
                 self.log.warning("Removing existing directory '%s'", course_path)
                 utils.rmtree(course_path)
             else:
-                self.fail(
-                    "Directory '{}' already exists! Rerun with --force to remove "
-                    "this directory first (warning: this will remove the ENTIRE "
-                    "directory and all files in it.) ".format(course_path))
+                if self._course_folder_content_exists(course_path):
+                    self.fail(
+                        "Directory '{}' and it's content already exists! Rerun with --force to remove "
+                        "this directory first (warning: this will remove the ENTIRE "
+                        "directory and all files in it.) ".format(course_path))
 
         # create the directory
         self.log.info("Creating directory '%s'...", course_path)
-        os.mkdir(course_path)
+
+        if not os.path.isdir(course_path):
+            os.mkdir(course_path)
 
         # populating it with an example
         self.log.info("Copying example from the user guide...")

Allow nbgrader quickstart to work on an existing directory
Rather than refusing to run if the directory exists, quickstart should check if it is going to overwrite any files and if it is only then throw an error.

cc @ncclementi
diff --git a/nbgrader/tests/apps/test_nbgrader_quickstart.py b/nbgrader/tests/apps/test_nbgrader_quickstart.py
index d9a9f705..f8257119 100644
--- a/nbgrader/tests/apps/test_nbgrader_quickstart.py
+++ b/nbgrader/tests/apps/test_nbgrader_quickstart.py
@@ -1,5 +1,5 @@
 import os
-import sys
+import shutil
 
 from .. import run_nbgrader
 from .base import BaseTestApp
@@ -39,6 +39,61 @@ class TestNbGraderQuickStart(BaseTestApp):
         # nbgrader assign should work
         run_nbgrader(["assign", "ps1"])
 
+    def test_quickstart_overwrite_course_folder_if_structure_not_present(self):
+        """Is the quickstart example properly generated?"""
+
+        run_nbgrader(["quickstart", "example_without_folder_and_config_file"])
+
+        # it should fail if it already exists
+        run_nbgrader(["quickstart", "example_without_folder_and_config_file"], retcode=1)
+
+        # should succeed if both source folder and config file are not present.
+        shutil.rmtree(os.path.join("example_without_folder_and_config_file", "source"))
+        os.remove(os.path.join("example_without_folder_and_config_file", "nbgrader_config.py"))
+
+        run_nbgrader(["quickstart", "example_without_folder_and_config_file"])
+        assert os.path.exists(os.path.join("example_without_folder_and_config_file", "nbgrader_config.py"))
+        assert os.path.exists(os.path.join("example_without_folder_and_config_file", "source"))
+
+        # nbgrader validate should work
+        os.chdir("example_without_folder_and_config_file")
+        for nb in os.listdir(os.path.join("source", "ps1")):
+            if not nb.endswith(".ipynb"):
+                continue
+            output = run_nbgrader(["validate", os.path.join("source", "ps1", nb)], stdout=True)
+            assert output.strip() == "Success! Your notebook passes all the tests."
+
+        # nbgrader assign should work
+        run_nbgrader(["assign", "ps1"])
+
+    def test_quickstart_fails_with_source_folder_removed(self):
+        """Is the quickstart example properly generated if source folder removed?"""
+
+        run_nbgrader(["quickstart", "example_source_folder_fail"])
+
+        # it should fail if it already exists
+        run_nbgrader(["quickstart", "example_source_folder_fail"], retcode=1)
+
+        # it should succeed if source folder not present and create it
+        shutil.rmtree(os.path.join("example_source_folder_fail", "source"))
+
+        # it should fail if it already source folder or config file exists
+        run_nbgrader(["quickstart", "example_source_folder_fail"], retcode=1)
+
+    def test_quickstart_fails_with_config_file_removed(self):
+        """Is the quickstart example properly generated if source folder removed?"""
+
+        run_nbgrader(["quickstart", "example_source_folder_fail"])
+
+        # it should fail if it already exists
+        run_nbgrader(["quickstart", "example_source_folder_fail"], retcode=1)
+
+        # it should succeed if source folder not present and create it
+        os.remove(os.path.join("example_source_folder_fail", "nbgrader_config.py"))
+
+        # it should fail if it already source folder or config file exists
+        run_nbgrader(["quickstart", "example_source_folder_fail"], retcode=1)
+
     def test_quickstart_f(self):
         """Is the quickstart example properly generated?"""
 

pip install -r dev-requirements.txt -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-rerunfailures coverage selenium invoke sphinx codecov cov-core nbval
pip install -U pip wheel setuptools
3.5
dev-requirements.txt dev-requirements-windows.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
alembic==1.7.7
anyio==3.6.2
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
async-generator==1.10
attrs==22.2.0
Babel==2.11.0
backcall==0.2.0
bleach==4.1.0
certifi==2021.5.30
cffi==1.15.1
charset-normalizer==2.0.12
codecov==2.1.13
comm==0.1.4
contextvars==2.4
cov-core==1.15.0
coverage==6.2
dataclasses==0.8
decorator==5.1.1
defusedxml==0.7.1
docutils==0.18.1
entrypoints==0.4
fuzzywuzzy==0.18.0
greenlet==2.0.2
idna==3.10
imagesize==1.4.1
immutables==0.19
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig==1.1.1
invoke==2.2.0
ipykernel==5.5.6
ipython==7.16.3
ipython-genutils==0.2.0
ipywidgets==7.8.5
jedi==0.17.2
Jinja2==3.0.3
json5==0.9.16
jsonschema==3.2.0
jupyter==1.1.1
jupyter-client==7.1.2
jupyter-console==6.4.3
jupyter-core==4.9.2
jupyter-server==1.13.1
jupyterlab==3.2.9
jupyterlab-pygments==0.1.2
jupyterlab-server==2.10.3
jupyterlab_widgets==1.1.11
Mako==1.1.6
MarkupSafe==2.0.1
mistune==0.8.4
nbclassic==0.3.5
nbclient==0.5.9
nbconvert==6.0.7
nbformat==5.1.3
-e git+https://github.com/jupyter/nbgrader.git@fa75fcb2fb7fa24451966b5ae2a44480f88d508b#egg=nbgrader
nbval==0.10.0
nest-asyncio==1.6.0
notebook==6.4.10
packaging==21.3
pandocfilters==1.5.1
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
pluggy==1.0.0
prometheus-client==0.17.1
prompt-toolkit==3.0.36
ptyprocess==0.7.0
py==1.11.0
pycparser==2.21
pyenchant==3.2.2
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-cov==4.0.0
pytest-rerunfailures==10.3
python-dateutil==2.9.0.post0
pytz==2025.2
pyzmq==25.1.2
requests==2.27.1
selenium==3.141.0
Send2Trash==1.8.3
six==1.17.0
sniffio==1.2.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
sphinxcontrib-spelling==7.7.0
SQLAlchemy==1.4.54
terminado==0.12.1
testpath==0.6.0
tomli==1.2.3
tornado==6.1
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
wcwidth==0.2.13
webencodings==0.5.1
websocket-client==1.3.1
widgetsnbextension==3.6.10
zipp==3.6.0

name: nbgrader
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - alembic==1.7.7
      - anyio==3.6.2
      - argon2-cffi==21.3.0
      - argon2-cffi-bindings==21.2.0
      - async-generator==1.10
      - attrs==22.2.0
      - babel==2.11.0
      - backcall==0.2.0
      - bleach==4.1.0
      - cffi==1.15.1
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - comm==0.1.4
      - contextvars==2.4
      - cov-core==1.15.0
      - coverage==6.2
      - dataclasses==0.8
      - decorator==5.1.1
      - defusedxml==0.7.1
      - docutils==0.18.1
      - entrypoints==0.4
      - fuzzywuzzy==0.18.0
      - greenlet==2.0.2
      - idna==3.10
      - imagesize==1.4.1
      - immutables==0.19
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - invoke==2.2.0
      - ipykernel==5.5.6
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - ipywidgets==7.8.5
      - jedi==0.17.2
      - jinja2==3.0.3
      - json5==0.9.16
      - jsonschema==3.2.0
      - jupyter==1.1.1
      - jupyter-client==7.1.2
      - jupyter-console==6.4.3
      - jupyter-core==4.9.2
      - jupyter-server==1.13.1
      - jupyterlab==3.2.9
      - jupyterlab-pygments==0.1.2
      - jupyterlab-server==2.10.3
      - jupyterlab-widgets==1.1.11
      - mako==1.1.6
      - markupsafe==2.0.1
      - mistune==0.8.4
      - nbclassic==0.3.5
      - nbclient==0.5.9
      - nbconvert==6.0.7
      - nbformat==5.1.3
      - nbval==0.10.0
      - nest-asyncio==1.6.0
      - notebook==6.4.10
      - packaging==21.3
      - pandocfilters==1.5.1
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pip==21.3.1
      - pluggy==1.0.0
      - prometheus-client==0.17.1
      - prompt-toolkit==3.0.36
      - ptyprocess==0.7.0
      - py==1.11.0
      - pycparser==2.21
      - pyenchant==3.2.2
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-rerunfailures==10.3
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyzmq==25.1.2
      - requests==2.27.1
      - selenium==3.141.0
      - send2trash==1.8.3
      - setuptools==59.6.0
      - six==1.17.0
      - sniffio==1.2.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-rtd-theme==2.0.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - sphinxcontrib-spelling==7.7.0
      - sqlalchemy==1.4.54
      - terminado==0.12.1
      - testpath==0.6.0
      - tomli==1.2.3
      - tornado==6.1
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - wcwidth==0.2.13
      - webencodings==0.5.1
      - websocket-client==1.3.1
      - widgetsnbextension==3.6.10
      - zipp==3.6.0
prefix: /opt/conda/envs/nbgrader

nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_quickstart_overwrite_course_folder_if_structure_not_present
nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_help nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_no_course_id nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_quickstart nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_quickstart_fails_with_source_folder_removed nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_quickstart_fails_with_config_file_removed nbgrader/tests/apps/test_nbgrader_quickstart.py::TestNbGraderQuickStart::test_quickstart_f
nbgrader/apps/quickstartapp.py
nbgrader/apps/quickstartapp.py